0

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.

Charlie Page
  • 541
  • 2
  • 17
  • Do you have toJson and fromJson also under enums? https://stackoverflow.com/questions/53035817/how-to-manage-serialize-deserialize-an-enum-property-with-dart-flutter-to-fi – Dominik Šimoník Sep 22 '22 at 13:39
  • I don't think it's necessary with this. When I look at the generated code, there is an enum map automatically created for those enums (they are defined in the same file). It looks like those are already specified. I tested with one enum without the custom type and it looks like the enum mapped to string just fine. – Charlie Page Sep 23 '22 at 12:00
  • Voting to close this question - this other question has exactly the same issue and answer: https://stackoverflow.com/questions/60595703/complex-models-with-json-serializable-listobjects-not-converting-to-map – Charlie Page Sep 23 '22 at 12:41

2 Answers2

1

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{
....
}

Afzal
  • 291
  • 3
  • 5
-1

u should be getting syntax as some properties are not defined but u are using them in the constructor?

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

  Habit({
    this.id,
    required this.title,
    // not defined??????
    this.description,
    // not defined?????? //etc...
    required this.userId,
    this.attachedDashboardId,
    this.attachedGoalId,
    required this.startDate,
    this.endDate,
    this.isActive = true,
    this.positivity = HabitPositivity.positive,
    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);
  }
}

Afzal
  • 291
  • 3
  • 5