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.