flutterdartgoogle-cloud-firestorejson-serializable

Using dart's json_serializable package with custom properties


Background

I'm trying to use the dart json_serializable package to encode/decode a custom type I'm writing/reading with Google Cloud Firestore, and I'm having an issue with the custom type having properties that are also custom types.

I'm storing a Habit which has two custom properties: HabitFrequency is a combination of an integer, and an enum frequencyType that I want to store together as a map in Firestore.

HabitCompletion is a list of the type HabitCompletion, which contains a date, completion status, and an optional string value/numeric value. I want this to be stored as an array of maps.

Issue

Whenever I try to save, I get this error:

[VERBOSE-2:dart_vm_initializer.cc(41)] Unhandled Exception: Invalid argument: Instance of 'HabitFrequency'

I have the top-level class and both custom classes used as properties (HabitFrequency and HabitCompletion) listed as JsonSerializable, and they have the appropriate fromJson/toJson methods specified.

Question

Is there a different way I need to set these up before I run build_runner build to generate the right code to serialize these properly?

Code Examples

Here is my top-level class:

@JsonSerializable()
class Habit {
  String? id;
  String title;
  @JsonKey(name: 'frequency')
  HabitFrequency frequency;
  List<HabitCompletion>? completions;

  Habit({
    this.id,
    required this.title,
    required this.frequency,
    this.completions,
  });

  /// Connect the generated [_$PersonFromJson] function to the `fromJson`
  /// factory.
  factory Habit.fromJson(Map<String, dynamic> json) => _$HabitFromJson(json);

  /// Connect the generated [_$PersonToJson] function to the `toJson` method.
  Map<String, dynamic> toJson() => _$HabitToJson(this);
  }
}

The Habit Frequency:

@JsonSerializable()
class HabitFrequency {
  int amount;
  HabitFrequencyType frequencyType;

  HabitFrequency(this.amount, this.frequencyType);

  /// Connect the generated [_$HabitFrequencyFromJson] function to the `fromJson`
  /// factory.
  factory HabitFrequency.fromJson(Map<String, dynamic> json) =>
      _$HabitFrequencyFromJson(json);

  /// Connect the generated [_$HabitFrequencyToJson] function to the `toJson` method.
  Map<String, dynamic> toJson() => _$HabitFrequencyToJson(this);
}

And the Habit completion:

@JsonSerializable()
class HabitCompletion {
  DateTime timestamp;
  bool completion;
  String? stringValue;
  double? numericValue;

  HabitCompletion(
      {required this.timestamp,
      required this.completion,
      this.stringValue,
      this.numericValue});

  /// Connect the generated [_$PersonFromJson] function to the `fromJson`
  /// factory.
  factory HabitCompletion.fromJson(Map<String, dynamic> json) =>
      _$HabitCompletionFromJson(json);

  /// Connect the generated [_$PersonToJson] function to the `toJson` method.
  Map<String, dynamic> toJson() => _$HabitCompletionToJson(this);
}

They each have one property that is just a simple enumeration.

If you want to see more of the code, here's a gist containing the file for this type (and the custom types used for the properties in questions), as well as the auto-generated code coming from json_serializable.


Solution

  • this can be resolved by setting explicitToJson on the JsonSerializable annotation for the Habit class as mentioned in your gist

    @JsonSerializable(explicitToJson: true)
    class Habit{
    ....
    }