Home > Net >  type is not subtype of type Map<String, dynamic>, @JsonSerializable()
type is not subtype of type Map<String, dynamic>, @JsonSerializable()

Time:05-23

I have some issue with my json serialization, I use json_serializable: ^6.2.0, so my code is:

shift.dart:

import 'package:hive/hive.dart';
import 'package:json_annotation/json_annotation.dart';

part 'shift.g.dart';

@HiveType(typeId: 1)
@JsonSerializable()
class Shift extends HiveObject {
  @HiveField(0)
  final DateTime? start;

  @HiveField(1)
  final DateTime? end;

  @HiveField(2, defaultValue: Duration(hours: 8))
  final Duration? duration;

  Shift({required this.start, required this.end, Duration? duration})
      : duration = duration ?? const Duration(hours: 8);

  Shift copyWith({
    DateTime? start,
    DateTime? end,
    Duration? duration,
  }) {
    return Shift(
      start: start ?? this.start,
      end: end ?? this.end,
      duration: duration ?? this.duration,
    );
  }

  Map<String, dynamic> toJson() => _$ShiftToJson(this);

  factory Shift.fromJson(Map<String, dynamic> json) => _$ShiftFromJson(json);

  // Equatable does not work with HiveObjects, necessary to override hashCode ==
  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is Shift &&
          runtimeType == other.runtimeType &&
          start == other.start &&
          end == other.end &&
          duration == other.duration;

  @override
  int get hashCode => start.hashCode ^ end.hashCode ^ duration.hashCode;
}

shift.g.dart:

// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'shift.dart';

// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************

class ShiftAdapter extends TypeAdapter<Shift> {
  @override
  final int typeId = 1;

  @override
  Shift read(BinaryReader reader) {
    final numOfFields = reader.readByte();
    final fields = <int, dynamic>{
      for (int i = 0; i < numOfFields; i  ) reader.readByte(): reader.read(),
    };
    return Shift(
      start: fields[0] as DateTime?,
      end: fields[1] as DateTime?,
      duration:
          fields[2] == null ? const Duration(hours: 8) : fields[2] as Duration?,
    );
  }

  @override
  void write(BinaryWriter writer, Shift obj) {
    writer
      ..writeByte(3)
      ..writeByte(0)
      ..write(obj.start)
      ..writeByte(1)
      ..write(obj.end)
      ..writeByte(2)
      ..write(obj.duration);
  }

  @override
  int get hashCode => typeId.hashCode;

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is ShiftAdapter &&
          runtimeType == other.runtimeType &&
          typeId == other.typeId;
}

// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************

Shift _$ShiftFromJson(Map<String, dynamic> json) => Shift(
      start: json['start'] == null
          ? null
          : DateTime.parse(json['start'] as String),
      end: json['end'] == null ? null : DateTime.parse(json['end'] as String),
      duration: json['duration'] == null
          ? null
          : Duration(microseconds: json['duration'] as int),
    );

Map<String, dynamic> _$ShiftToJson(Shift instance) => <String, dynamic>{
      'start': instance.start?.toIso8601String(),
      'end': instance.end?.toIso8601String(),
      'duration': instance.duration?.inMicroseconds,
    };

shift_cubit.dart:

import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:timer/cubit/shift/shift_state.dart';

import '../../model/shift.dart';

class ShiftCubit extends HydratedCubit<ShiftState> {
  ShiftCubit()
      : super(
          ShiftState(
            shift: Shift(
              start: null,
              end: null,
              duration: const Duration(hours: 8),
            ),
            enabledStart: true,
            enabledEnd: false,
            status: ShiftStateStatus.initial,
          ),
        );

  void updateStart(DateTime start) {
    emit(
      ShiftState(
        shift: Shift(
          start: start,
          end: null,
          duration: const Duration(hours: 8),
        ),
        enabledStart: false,
        enabledEnd: true,
        status: ShiftStateStatus.startTapped,
      ),
    );
  }

  void updateEnd(DateTime end) {
    emit(
      ShiftState(
        shift: Shift(
          start: state.shift.start,
          end: end,
          duration: const Duration(hours: 8),
        ),
        enabledStart: false,
        enabledEnd: false,
        status: ShiftStateStatus.endTapped,
      ),
    );
  }

  void resetNewDay() {
    emit(
      ShiftState(
        shift: Shift(
          start: null,
          end: null,
          duration: const Duration(hours: 8),
        ),
        enabledStart: true,
        enabledEnd: false,
        status: ShiftStateStatus.initial,
      ),
    );
  }

  @override
  ShiftState fromJson(Map<String, dynamic> json) {
    return ShiftState.fromJson(json);
  }

  @override
  Map<String, dynamic> toJson(ShiftState state) {
    return state.toJson();
  }
}

shift_state.dart:

import 'dart:convert';

import 'package:equatable/equatable.dart';
import 'package:json_annotation/json_annotation.dart';

import '../../model/shift.dart';

part 'shift_state.g.dart';

enum ShiftStateStatus { initial, startTapped, endTapped }

@JsonSerializable()
class ShiftState extends Equatable {
  final Shift shift;
  final bool enabledStart;
  final bool enabledEnd;
  final ShiftStateStatus status;

  const ShiftState({
    required this.shift,
    required this.enabledStart,
    required this.enabledEnd,
    required this.status,
  });

  @override
  List<Object> get props => [shift, enabledStart, enabledEnd, status];

  Map<String, dynamic> toJson() => _$ShiftStateToJson(this);

  factory ShiftState.fromJson(Map<String, dynamic> json) =>
      _$ShiftStateFromJson(json);
}

shift_state.g.dart:

// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'shift_state.dart';

// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************

ShiftState _$ShiftStateFromJson(Map<String, dynamic> json) => ShiftState(
      shift: Shift.fromJson(json['shift'] as Map<String, dynamic>),
      enabledStart: json['enabledStart'] as bool,
      enabledEnd: json['enabledEnd'] as bool,
      status: $enumDecode(_$ShiftStateStatusEnumMap, json['status']),
    );

Map<String, dynamic> _$ShiftStateToJson(ShiftState instance) =>
    <String, dynamic>{
      'shift': instance.shift,
      'enabledStart': instance.enabledStart,
      'enabledEnd': instance.enabledEnd,
      'status': _$ShiftStateStatusEnumMap[instance.status],
    };

const _$ShiftStateStatusEnumMap = {
  ShiftStateStatus.initial: 'initial',
  ShiftStateStatus.startTapped: 'startTapped',
  ShiftStateStatus.endTapped: 'endTapped',
};

and I try to test my code like:

group('toJson/fromJson', () {
      test('work properly', () async {
        await mockHydratedStorage(() {
          final shiftCubit = ShiftCubit();
          expect(
            shiftCubit.fromJson(shiftCubit.toJson(shiftCubit.state)),
            shiftCubit.state,
          );
        });
      });
    });

And the result is: type 'Shift' is not a subtype of type 'Map<String, dynamic>' in type cast The application seems to work well, but I wonder why this serialization does not work.

Edit:

Stacktrace:

package:timer/cubit/shift/shift_state.g.dart 10:43  _$ShiftStateFromJson
package:timer/cubit/shift/shift_state.dart 32:7     new ShiftState.fromJson
package:timer/cubit/shift/shift_cubit.dart 68:23    ShiftCubit.fromJson
test\bloc\bloc_test.dart 40:24                      main.<fn>.<fn>.<fn>.<fn>
package:hydrated_bloc/src/hydrated_bloc.dart 62:22  HydratedBlocOverrides.runZoned.<fn>.<fn>

type 'Shift' is not a subtype of type 'Map<String, dynamic>' in type cast

CodePudding user response:

The problem occurs in shift_state.g.dart:

ShiftState _$ShiftStateFromJson(Map<String, dynamic> json) => ShiftState(
      shift: Shift.fromJson(json['shift'] as Map<String, dynamic>),
      enabledStart: json['enabledStart'] as bool,
      enabledEnd: json['enabledEnd'] as bool,
      status: $enumDecode(_$ShiftStateStatusEnumMap, json['status']),
    );

Map<String, dynamic> _$ShiftStateToJson(ShiftState instance) =>
    <String, dynamic>{
      'shift': instance.shift,
      'enabledStart': instance.enabledStart,
      'enabledEnd': instance.enabledEnd,
      'status': _$ShiftStateStatusEnumMap[instance.status],
    };

Note that your toJson method is populating the value whose key is 'shift' with instance.shift, but your fromJson is expecting a Map, not an instance object of type Shift. The fix is simple, you need to set explicitToJson to true like:

@JsonSerializable(explicitToJson: true)
class ShiftState extends Equatable {

Note from the docs:

{bool? explicitToJson}
If true, generated toJson methods will explicitly call toJson on nested objects.

When using JSON encoding support in dart:convert, toJson is automatically called on objects, so the default behavior (explicitToJson: false) is to omit the toJson call.

Note that this is exactly what we want, we want toJson called on nested objects in your case.

  • Related