Skip to content

[bug] json_serializable is unable to recognize generics on @JsonKey's toJson/fromJson methods, or on JsonConverter #1508

@lucavenir

Description

@lucavenir

TL;DR: minimal reproducible bug:

class A<T> {}

A<T> fromJson<T>(String input) => A<T>();
String toJson<T>(A<T> input) => '$input';

@JsonSerializable()
class Serializable<T> {
  const Serializable({required this.a});
  @JsonKey(fromJson: fromJson, toJson: toJson)
  final A<T> a;
}

The above breaks the builder.


In an attempt to work around #1507, I end up with the following:

sealed class Slug<T> {}
class Asd extends Slug<String> {}
class Lol extends Slug<int> {}
class Rofl extends Slug<double> {}
class Lmao extends Slug<(int, int)> {}

@JsonSerializable()
class Serializable<T> {
  const Serializable(this.slug);
  final Slug<T> slug;
}

The above outputs:

[SEVERE] json_serializable on lib/src/features/models/lol_dto.dart:

Could not generate `fromJson` code for `slug`.
To support the type `Slug` you can:
* Use `JsonConverter`
  https://pub.dev/documentation/json_annotation/latest/json_annotation/JsonConverter-class.html
* Use `JsonKey` fields `fromJson` and `toJson`
  https://pub.dev/documentation/json_annotation/latest/json_annotation/JsonKey/fromJson.html
  https://pub.dev/documentation/json_annotation/latest/json_annotation/JsonKey/toJson.html
package:asdlol/src/features/models/lol_dto.dart:19:17
   ╷
19 │   final Slug<T> slug;
   │                 ^^^^
   ╵

But that's expected.

So, I follow the first advice, the JsonConverter way.

@JsonSerializable()
class Serializable<T> {
  const Serializable(this.slug);
  @SlugConverter<T>() // this has been added
  final Slug<T> slug;
}

class SlugConverter<T> extends JsonConverter<Slug<T>, String> {
  const SlugConverter();

  @override
  String toJson(Slug<T> object) {
    return object.runtimeType.toString(); 
  }

  @override
  Slug<T> fromJson(String json) {
    switch (json) {
      case 'Asd':
        return Asd() as Slug<T>;
      case 'Lol':
        return Lol() as Slug<T>;
      case 'Rofl':
        return Rofl() as Slug<T>;
      case 'Lmao':
        return Lmao() as Slug<T>;
      default:
        throw Exception('Unknown slug type: $json');
    }
  }
}

But the above is straight ignored by the builder, which outputs exactly the same message.
Am I doing something wrong? If so, can I be welcomed with a more informative message? 😸

Then, I try the JsonKey way.

@JsonSerializable()
class Serializable<T> {
  const Serializable(this.slug);
  @JsonKey(fromJson: slugFromJson, toJson: slugToJson) // let's try this one now
  final Slug<T> slug;
}

String slugToJson<T>(Slug<T> object) {
  return object.runtimeType.toString();
}

Slug<T> slugFromJson<T>(String json) {
  switch (json) {
    case 'Asd':
      return Asd() as Slug<T>;
    case 'Lol':
      return Lol() as Slug<T>;
    case 'Rofl':
      return Rofl() as Slug<T>;
    case 'Lmao':
      return Lmao() as Slug<T>;
    default:
      throw Exception('Unknown slug type: $json');
  }
}

But the above leads the builder to output:

[SEVERE] json_serializable on lib/src/features/models/lol_dto.dart:

Error with `@JsonKey` on the `slug` field. The `toJson` function `slugToJson` argument type `Slug<T>` is not compatible with field type `Slug<T>`.
package:asdlol/src/features/models/lol_dto.dart:19:17
   ╷
19 │   final Slug<T> slug;
   │                 ^^^^
   ╵

Say again? Slug<T> is not compatible with Slug<T>? 😵‍💫

I'm unsure what I can actually do at this point.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions