google / json_serializable.dart

Generates utilities to aid in serializing to/from JSON.
https://pub.dev/packages/json_serializable
BSD 3-Clause "New" or "Revised" License
1.54k stars 391 forks source link

Generics complicate self-referential class structure #1415

Open craiglabenz opened 2 months ago

craiglabenz commented 2 months ago

The following code builds, but see the comment for which critical information has to be removed for said build to succeed:

class Base<T> {
  const Base();
}

class BaseConverter<T> extends JsonConverter<Base, Map<String, Object?>> {
  const BaseConverter();
  @override
  Map<String, Object?> toJson(Base object) => {};
  @override
  Base fromJson(Map<String, Object?> json) => Base();
}

@JsonSerializable()
class Data<T> extends Base<T> {
  Data({
    required this.data,
    required this.name,
  });

  factory Data.fromJson(Map<String, dynamic> json) => _$DataFromJson(json);

  final String name;

  @BaseConverter()
  // Ideally, this would be Base<T>, but defining it so breaks the build
  final Base data;

  Map<String, dynamic> toJson() => _$DataToJson(this);
}

Changing final Base data to final Base<T> data results in this error:

[SEVERE] json_serializable on lib/src/data.dart:

Could not generate `fromJson` code for `data`.
To support the type `Base` 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:json_serializable_test/src/data.dart:30:17
   ╷
[line number] │   final Base<T> data;

This despite the fact that a JsonConverter is clearly being used.

For posterity, I originally reported this at rrousselGit/freezed#1074, and was (correctly, it seems) directed here.

i-panov commented 3 weeks ago

I have same problem :c

gnprice commented 4 days ago
class BaseConverter<T> extends JsonConverter<Base, Map<String, Object?>> {

What happens if you write Base<T> there, instead of Base?

Writing just Base with no arguments is equivalent, IIUC, to writing Base<dynamic>. Which doesn't seem like what you intend. And it makes sense that a converter that produces a Base<dynamic> wouldn't be seen as a match when the field is supposed to hold more specifically a Base<T>.

That said, I won't be surprised if that doesn't fix it. Here's a possibly related issue:

which suggests that the logic for applying a JsonConverter doesn't currently do everything one would hope it would when there are type parameters involved.