3

I have freezed model (simplified):

part 'initial_data_model.freezed.dart';
part 'initial_data_model.g.dart';

@freezed
class InitialDataModel with _$InitialDataModel {
  const factory InitialDataModel() = Data;

  const factory InitialDataModel.loading() = Loading;

  const factory InitialDataModel.error([String? message]) = Error;

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

documentation says how to assign custom converters on fields but not on model itself

I got json from backend and somewhere in api_provider I do
return InitialDataModel.fromJson(json);
I have no control on json structure, there aren't "runtimeType" and other stupid redundant things

when I want to create a model from json I call fromJson I'm having this

flutter: CheckedFromJsonException
Could not create `InitialDataModel`.
There is a problem with "runtimeType".
Invalid union type "null"!

ok, again
I have api_provider

final apiProvider = Provider<_ApiProvider>((ref) => _ApiProvider(ref.read));

class _ApiProvider {
  final Reader read;

  _ApiProvider(this.read);

  Future<InitialDataModel> fetchInitialData() async {
    final result = await read(repositoryProvider).send('/initial_data');
    return result.when(
      (json) => InitialDataModel.fromJson(json),
      error: (e) => InitialDataModel.error(e),
    );
  }
}

you may see I'm trying to create InitialDataModel from json

this line throws an error I mentioned above

I don't understand how to create InitialDataModel from json, now in my example it's just empty model, there are no fields

(json) => InitialDataModel.fromJson(json),
json here is Map, it shows an error even if I pass simple empty map {} instead of real json object

Nagual
  • 1,576
  • 10
  • 17
  • 27
  • do you use the `serializable` package for generating the `fromJson` method? – tmaihoff Oct 25 '21 at 08:11
  • And why do you need a custom converter for the whole model? It should be sufficient to annotate every non-primitive field with a custom converter – tmaihoff Oct 25 '21 at 08:13
  • you need to add `json_serializable` to your `pubspec` `dev_dependencies`. This package will generate the required code for json conversion if you have the `fromJson` factory constructor in your freezed class. And your non-primitive fields have to be annotated with the custom json-converters – tmaihoff Oct 25 '21 at 08:18
  • There seems to be a problem with your types. The conversion only works with primitive types like int, string, double or with annotated non-primitives. So you have to write a json-converter for `CountryModel` and for `CityModel` and annotate these fields accordingly. – tmaihoff Oct 25 '21 at 08:25
  • which type has the `json` variable you want to convert? `String` or `Map`? If it is `String` you have to convert it first https://flutter.dev/docs/development/data-and-backend/json#serializing-json-inside-model-classes – tmaihoff Oct 25 '21 at 08:45

1 Answers1

5

The easiest solution is to use the correct constructor instead of _$InitialDataModelFromJson. Example:

@freezed
class InitialDataModel with _$InitialDataModel {
  const factory InitialDataModel() = Data;

  ...

  factory InitialDataModel.fromJson(Map<String, dynamic> json) => Data.fromJson(json);
}

The drawback of course is that you can only use the fromJson when you're sure you have the correct json, which isn't great. I actually wouldn't recommend this way because it leaves to the caller the burden of checking the validity and calling the correct constructor.


Another solution, maybe the best, is to follow the documentation and create a custom converter, even though this would require you to have two separated classes.


Otherwise you could chose a different approach and separate the data class from the union, so you'll have a union used just for the state of the request and a data class for the success response:

@freezed
class InitialDataModel with _$InitialDataModel {
  factory InitialDataModel(/* here go your attributes */) = Data;

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

@freezed
class Status with _$Status {
  const factory Status.success(InitialDataModel model) = Data;
  const factory Status.loading() = Loading;
  const factory Status.error([String? message]) = Error;
}

and then

[...]
    return result.when(
      (json) => Status.success(InitialDataModel.fromJson(json)),
      error: (e) => Status.error(e),
    );
[...]

Giovanni Londero
  • 1,309
  • 1
  • 10
  • 20
  • 1
    oooo man, I saved my life!!!! I spent whole day on it and you're totally right, I just changed `=> _$InitialDataModelFromJson(json);` to `=> _$Data.fromJson(json);` and it's working now! my real code much much complex then this example but I didn't change any extra lines, the reason was in constructor as you said, thanks a lot – Nagual Oct 25 '21 at 18:09
  • 1
    That's great! I improved my answer anyway to add some opinions and another solution in case someone else needs this – Giovanni Londero Oct 26 '21 at 10:34
  • i got some issue when implement nested object response with one dart , i only can work with separate file but the documentation determine we can on just 1 file i mean `const factory Data.root` and `const factory Data.child` . still on problem – Yogi Arif Widodo May 13 '22 at 13:50
  • @YogiArifWidodo too little information sorry, please open a new question (also, I haven't played with Dart for some months, so I'm not sure I can help) – Giovanni Londero May 16 '22 at 07:28