Json_serializable.dart: Nested Generics

Created on 30 May 2018  Â·  29Comments  Â·  Source: google/json_serializable.dart

i think this his highly related to this issue

but i have some trouble using it for my needs. I either use it wrong or it is currently not possible.

this is an example of what i want to parse

{
  "status": "success",
  "message": null,
  "code": 0,
  "data": {
    "timestamp": 0,
    "result": [
      {
        "id": 409,
        "active": true,
        "accessToken": "12345_abc",
      }
    ]
  }
}

and here the corresponding classes

@JsonSerializable()
class WsResponse<T> extends Object with _$WsResponseSerializerMixin<T> {

  String status;
  String message;
  int code;
  @JsonKey(fromJson: _dataFromJson, toJson: _dataToJson)
  WsData<T> data;

  WsResponse({this.status, this.message, this.code, this.data});

  factory WsResponse.fromJson(Map<String, dynamic> json) => _$WsResponseFromJson<T>(json);
}

here i have an outer "class" with status message and code that works like every ordinary class.

@JsonSerializable()
class WsData<T> extends Object with _$WsDataSerializerMixin<T> {
  int timestamp;
  @JsonKey(fromJson: _resultFromJson, toJson: _resultToJson)
  List<T> result;

  WsData({this.timestamp, this.result});

  factory WsData.fromJson(Map<String, dynamic> json) => _$WsDataFromJson<T>(json);
}
@JsonSerializable()
class WsLogin extends Object with _$WsLoginSerializerMixin {
  int id;
  bool active;

  String accessToken;

  WsLogin({this.id, this.active, this.accessToken,});

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

I omitted the fromJson etc. definitions because i don't know how to express them right either (may be the part where i am wrong)

but if i try _dataFromJson for example

WsData<T> _dataFromJson<T>(Map<String, dynamic> input) {
  return input['data'] as WsData<T>;
}

i get an error with

[SEVERE] json_serializable on lib/webservice/webservice.dart:
Error running JsonSerializableGenerator
Error with `@JsonKey` on `data`. The `fromJson` function `_dataFromJson` return type `WsData<T>` is not compatible with field type `WsData<T>`.
package:crewlove/webservice/webservice.dart:31:13
  WsData<T> data;
            ^^^^

which is surprising for me at first. Omitting the @JsonKey annotations does not help either as i think its necessary for generics. But i don't know how to express my intent here.

My intent is to use it like this

var wsLoginResp = new WsResponse<WsLogin>.fromJson(json.decode(resp));  

Most helpful comment

I'm trying to achieve something similar to create a generic object that can be used for endpoints that return a collection/page of objects.

{
  "page": 1,
  "results": [
     .... objects ....
  ],
  "total_results": 14,
  "total_pages": 1
}
@JsonSerializable()
class ResponseCollection<T> extends Object
    with _$ResponseCollectionSerializerMixin<T> {
  @JsonKey(name: "page")
  final int page;
  @JsonKey(name: "total_results")
  final int totalResults;
  @JsonKey(name: "total_pages")
  final int totalPages;
  @JsonKey(fromJson: resultFromJson, toJson: _resultToJson)
  final List<T> results;

  ResponseCollection(
      this.page, this.totalResults, this.totalPages, this.results);

  factory ResponseCollection.fromJson(Map<String, dynamic> json) =>
      _$ResponseCollectionFromJson<T>(json);
}

List<T> resultFromJson<T>(List input) {
  // looking for a way to check what T is we can call the correct Class.formJson()
  return input.map((f) {
    if (T is MovieSearchResult) {
      return MovieSearchResult.fromJson(f);
    } else if (T is Movie) {
      return Movie.fromJson(f);
    } else {
      throw Exception("Unknow type");
    }
  });
}

Map<String, dynamic> _resultToJson<T>(List<T> input) => {'results': input};

maybe you can do complexly like this ?

List<T> resultFromJson<T>(List input) {
  // looking for a way to check what T is we can call the correct Class.formJson()
  String _class = T.toString();
  return input.map((f) {
    if (_class == 'MovieSearchResult') {
      return MovieSearchResult.fromJson(f);
    } else if (_class == 'Movie') {
      return Movie.fromJson(f);
    } else {
      throw Exception("Unknow type");
    }
  });
}

All 29 comments

Update this code:

WsData<T> _dataFromJson<T>(Map<String, dynamic> input) {
  return input['data'] as WsData<T>;
}

to

T _dataFromJson<T>(Map<String, dynamic> input) {
  return input['data'] as T;
}

(Although you'll need to put in some sort of helper here – since the input JSON is not WsData<T> or whatever....

Closing this to get it off my queue. Please reopen if you still have issues...

I'm trying to achieve something similar to create a generic object that can be used for endpoints that return a collection/page of objects.

{
  "page": 1,
  "results": [
     .... objects ....
  ],
  "total_results": 14,
  "total_pages": 1
}
@JsonSerializable()
class ResponseCollection<T> extends Object
    with _$ResponseCollectionSerializerMixin<T> {
  @JsonKey(name: "page")
  final int page;
  @JsonKey(name: "total_results")
  final int totalResults;
  @JsonKey(name: "total_pages")
  final int totalPages;
  @JsonKey(fromJson: resultFromJson, toJson: _resultToJson)
  final List<T> results;

  ResponseCollection(
      this.page, this.totalResults, this.totalPages, this.results);

  factory ResponseCollection.fromJson(Map<String, dynamic> json) =>
      _$ResponseCollectionFromJson<T>(json);
}

List<T> resultFromJson<T>(List input) {
  // looking for a way to check what T is we can call the correct Class.formJson()
  return input.map((f) {
    if (T is MovieSearchResult) {
      return MovieSearchResult.fromJson(f);
    } else if (T is Movie) {
      return Movie.fromJson(f);
    } else {
      throw Exception("Unknow type");
    }
  });
}

Map<String, dynamic> _resultToJson<T>(List<T> input) => {'results': input};

@aegis123 – does that work for you? You'd need to be running the VM w/ --preview-dart-2 so that the generic parameter to resultFromJson shows up...

Thats should be enable by default in beta 3 right? Or do I have to add --preview-dart-2 to flutter packages pub run build_runner build make it use the dart 2 preview?

You need a version of Flutter where build_runner is running w/ --preview-dart-2. That should be coming soon...

Is that in preview 1 available?

I don't think so...

@kevmoo or @aegis123 : Do you have any working example where the class has a List of generics?

I can't achieve that @aegis123 wants (generic object that can be used for endpoints that return a collection/page of objects).

I checked the examples and the tests of json_serializable, but I can't figure out how to use it with List of generics.

Let me hack on this now...

I'm working on a fix for this now...

Closing this out for https://github.com/dart-lang/json_serializable/issues/308 – trying to brainstorm a solution. Please comment!

CC @pythoneer @mecseid @aegis123 – I think the solution in https://github.com/dart-lang/json_serializable/pull/312 is pretty flexible.

Take a look!

pls see my comment on the pull request

@aegis123 – once we publish, I'll update the example package w/ usage...examples...

@kevmoo do you have an example on how to use this?

@aegis123 – please sketch out the the classes you want to write and I'll create an example of how to serialize – hopefully. 😄

@JsonSerializable()
class GenericCollection<T> {
  @JsonKey(name: 'page')
  final int page;
  @JsonKey(name: 'total_results')
  final int totalResults;
  @JsonKey(name: 'total_pages')
  final int totalPages;
  @JsonKey(name: 'results')
  final List<T> results;

  GenericCollection(
      {this.page, this.totalResults, this.totalPages, this.results});

  factory GenericCollection.fromJson(Map<String, dynamic> json) =>
      _$GenericCollectionFromJson<T>(json);
}

and it should be able to be called like final reviewCollection = GenericCollection<Review>.fromJson(someJson) or if you call another endpoint GenericCollection<Movie>.fromJson(someJson). Then there should probably be an @GenericConverter() on the GenericCollection<T> class if I have read the code correctly which contains the implementation on checking the class/instance type of T and calling the correct factory for the type like Movie.fromJson(json) or Review.fromJson(json).

@kevmoo If your quick I might be able to add to a talk/coding demo I'm giving at Mobilization tomorrow 😉

Thx 😄.

For Flutter there is no way to do something like

class CustomType {}

void convert<T>(Object obj) {
   if( obj instanceOf CustomType) {
        return CustomType();
    }
}

I'm trying to achieve something similar to create a generic object that can be used for endpoints that return a collection/page of objects.

{
  "page": 1,
  "results": [
     .... objects ....
  ],
  "total_results": 14,
  "total_pages": 1
}
@JsonSerializable()
class ResponseCollection<T> extends Object
    with _$ResponseCollectionSerializerMixin<T> {
  @JsonKey(name: "page")
  final int page;
  @JsonKey(name: "total_results")
  final int totalResults;
  @JsonKey(name: "total_pages")
  final int totalPages;
  @JsonKey(fromJson: resultFromJson, toJson: _resultToJson)
  final List<T> results;

  ResponseCollection(
      this.page, this.totalResults, this.totalPages, this.results);

  factory ResponseCollection.fromJson(Map<String, dynamic> json) =>
      _$ResponseCollectionFromJson<T>(json);
}

List<T> resultFromJson<T>(List input) {
  // looking for a way to check what T is we can call the correct Class.formJson()
  return input.map((f) {
    if (T is MovieSearchResult) {
      return MovieSearchResult.fromJson(f);
    } else if (T is Movie) {
      return Movie.fromJson(f);
    } else {
      throw Exception("Unknow type");
    }
  });
}

Map<String, dynamic> _resultToJson<T>(List<T> input) => {'results': input};

maybe you can do complexly like this ?

List<T> resultFromJson<T>(List input) {
  // looking for a way to check what T is we can call the correct Class.formJson()
  String _class = T.toString();
  return input.map((f) {
    if (_class == 'MovieSearchResult') {
      return MovieSearchResult.fromJson(f);
    } else if (_class == 'Movie') {
      return Movie.fromJson(f);
    } else {
      throw Exception("Unknow type");
    }
  });
}

When I call the api like

final HttpResponseDataListModel<SubjectBasicModel> result =
  await Http.get<HttpResponseDataListModel<SubjectBasicModel>>(url, params: params);

I will get this error:

_CastError(type 'HttpResponseDataListModel<dynamic>' is not a subtype of type 'HttpResponseDataListModel<SubjectBasicModel>' in type cast)

seems it can't take use of the nested generic. Should I just don't use the nested generic while calling apis or manually test the types of the nested data?

is there a better way to do this?

Does anyone have an example of generics working? Honestly I have no idea how they possibly could work unless we had some kind of abstract class called JsonSerializableOutput or something like that

@JsonSerializable(fieldRename: FieldRename.snake)
class TwicApiResponse<T> {
  TwicApiResponse(this.data, this.errors);

  final T data;
  final List<_Error> errors;

  factory TwicApiResponse.fromJson(Map<String, dynamic> json) =>
      _$TwicApiResponseFromJson(json);
}
[SEVERE] json_serializable:json_serializable on lib/services/twic_rest_service/models/models.dart:
Error running JsonSerializableGenerator
Could not generate `fromJson` code for `data`.
None of the provided `TypeHelper` instances support the defined type.
package:twic/services/twic_rest_service/models/response.dart:20:11
   â•·
20 │   final T data;
   │           ^^^^
   ╵

I'm just going to say my data type is Map and unwrap it one more time when I do my fetching operations.

I'm just going to say my data type is Map and unwrap it one more time when I do my fetching operations.

That's a completely fine solution

Thanks for the help, Kevin!

https://stackoverflow.com/questions/55306746/how-to-use-generics-and-list-of-generics-with-json-serialization-in-dart

not quite the cleanest way.. that violates the concept of generic. any data class with serialization should be acceptable – uzu Mar 4 at 6:59

Take a look at https://github.com/dart-lang/json_serializable/blob/master/example/lib/json_converter_example.dart

this example seems to be the cleanest (?) way yet, but violates the concepts of generic and definitely not the best way.. need dart's official support for this

https://github.com/dart-lang/sdk/issues/40873

Take a look at https://github.com/dart-lang/json_serializable/blob/master/example/lib/json_converter_example.dart

also, with this solution it cannot deal with data strucuture of List<T>.

List<T> it self is another generic type, lets say, A.

A.containsKey("key")?? its just non sense

Was this page helpful?
0 / 5 - 0 ratings

Related issues

HosseinArabbeigi picture HosseinArabbeigi  Â·  3Comments

enyo picture enyo  Â·  6Comments

Nico04 picture Nico04  Â·  5Comments

xiang23 picture xiang23  Â·  5Comments

broofus picture broofus  Â·  6Comments