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.55k stars 395 forks source link

toJson errors when using generic + the toJson is in a mixin #1297

Open fzyzcjy opened 1 year ago

fzyzcjy commented 1 year ago

Cross-posted in https://github.com/rrousselGit/freezed/issues/882, please see description there :)

kevmoo commented 1 year ago

Please provide a json_serializable-only repo. Input, actual output vs expected.

Thanks!

fzyzcjy commented 1 year ago

Sure, will do it later :)

nickveliki commented 1 year ago

it's not EXACTLY what's described in the issue, but generic type JsonConverters don't work as desired for me, either and I think this is the main issue

input class

@JsonSerializable()
class Praxis extends JsonSerializable {
  Praxis(
      {required this.name,
      required this.doctors,
      required this.betriebsStaetten,
      required this.hcmKundenNummer,
      required this.portalExportDate,
      required this.id});
  final String name;
  @ForeignFetchConverter<Arzt>()
  final List<ForeignFetch<Arzt>> doctors;
  @ForeignFetchConverter<BetriebsStaette>()
  final List<ForeignFetch<BetriebsStaette>> betriebsStaetten;
  final String hcmKundenNummer;
  final DateTime portalExportDate;
  final int id;

  @override
  Map<String, dynamic> toJson() => _$PraxisToJson(this);

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

  @override
  String toString() => jsonEncode(this);

  factory Praxis.fromString(String json) => Praxis.fromJson(jsonDecode(json));
}

Converter & Converted generic class

class ForeignFetch<T extends JsonSerializable> {
  final String foreignKey;
  final Object fkValue;

  Map<String, dynamic> toJson() {
    return {"foreignKey": foreignKey, "fkValue": fkValue};
  }

  factory ForeignFetch.fromJson(Map<String, dynamic> json) {
    return ForeignFetch(
        foreignKey: json["foreignKey"]!, fkValue: json["fkValue"]!);
  }

  static bool isForeignFetch(Map<String, dynamic> json) {
    if (json["foreignKey"] is String && json["fkValue"] is Object) {
      return true;
    }
    return false;
  }

  ForeignFetch({required this.foreignKey, required this.fkValue});

  Future<T?> getForeignEntity(DataService<T> service) async {
    try {
      return (await service
              .fetchData([FilterCondition(foreignKey, Equality.EQ, fkValue)]))
          .first;
    } catch (e) {
      return null;
    }
  }
}

class ForeignFetchConverter<T extends JsonSerializable>
    extends JsonConverter<ForeignFetch<T>, Map<String, dynamic>> {
  const ForeignFetchConverter();

  @override
  ForeignFetch<T> fromJson(Map<String, dynamic> json) {
    return ForeignFetch.fromJson(json);
  }

  @override
  Map<String, dynamic> toJson(ForeignFetch<T> object) {
    return object.toJson();
  }
}

got

Map<String, dynamic> _$PraxisToJson(Praxis instance) => <String, dynamic>{
      'name': instance.name,
      'doctors':
          instance.doctors,
      'betriebsStaetten': instance.betriebsStaetten,
      'hcmKundenNummer': instance.hcmKundenNummer,
      'portalExportDate': instance.portalExportDate.toIso8601String(),
      'id': instance.id,
    };

expected

Map<String, dynamic> _$PraxisToJson(Praxis instance) => <String, dynamic>{
      'name': instance.name,
      'doctors':
          instance.doctors.map(const ForeignFetchConverter<Arzt>().toJson),
      'betriebsStaetten': instance.betriebsStaetten
          .map(const ForeignFetchConverter<BetriebsStaette>().toJson),
      'hcmKundenNummer': instance.hcmKundenNummer,
      'portalExportDate': instance.portalExportDate.toIso8601String(),
      'id': instance.id,
    };

interesingly enough, the _$PraxisFromJson does EXACTLY what I want it to do

kevmoo commented 1 year ago

Why are you extending JsonSerializable?

kevmoo commented 1 year ago

Playing with extending JsonConverter with generics – you're doing a lot of stuff that this package was never designed for. You're mileage may vary!

fzyzcjy commented 1 year ago

(Btw that sample seems unrelated to me, not sure whether seems like a separate issue. For my own scenario, I use the workaround mentioned in https://github.com/rrousselGit/freezed/issues/882#issue-1640809279 to fix it)

nickveliki commented 1 year ago

@kevmoo why extend jsonserializeable?

because I'm lazy, and I needed a super class to collect items with the toJson method.

you're doing a lot of stuff that this package was never designed for

I'd love to contribute but I've actually just started with dart two weeks ago. I really need serializeable generics in this project, though, so I'm doing what is explicitly NOT RECOMMENDED and editing the generated files to call the toJson function on the members. I would like to be able to avoid doing unrecommended stuff...

@fzyzcjy why does it seem related?

The issue as I understand it is that generics with toJson are not being analyzed correctly. Whether the toJson is inherited via mixin or class seems irrelevant