Custom JsonConverter with different types in toJson and fromJson

525 Views Asked by At

In order to read from and write data with relations to the PocketBase API I need custom fromJson and toJson methods to de/serialize it to my models:


@freezed
class MyModel with _$MyModel {
  const factory MyModel({
    String name,
    @RelationsConverter() List<Relation>? relations,
  }) = _MyModel;

  factory MyModel.fromJson(Map<String, Object?> json) => _$MyModelFromJson(json);
}

@freezed
class Relation with _$Relation {
  const factory Relation({
    required String id,
    String name,
  }) = _Relation;

  factory Relation.fromJson(Map<String, Object?> json) => _$RelationFromJson(json);
}

The Json data when reading a model from PocketBase with the fields "name" and "relations", which contains a list of relations to some other model might look like this (with the expand=relations query parameter set):

{
    "name": "A model",
    "relations": [
        "abc123",
        "def456"
    ],
    "expand": {
        "relations": [
            {
                "id": "abc123",
                "name": "Relation A"
            },
            {
                "id": "def456",
                "name": "Relation B"
            }
        ]
    }
}

Before converting the data to my models, I transform the data so it looks like this and can be easily deserialized:

{
    "name": "A model",
    "relations": [
        {
            "id": "abc123",
            "name": "Relation A"
        },
        {
            "id": "def456",
            "name": "Relation B"
        }
    ]
}

When updating/creating data however, this form is not desired, I need it in this form:

{
    "name": "An updated model with a new relation",
    "relations": [
        "abc123",
        "def456",
        "xyz999"
    ]
}

I was hoping this would be trivial with a custom converter. The T type is used as a placeholder since this is my main problem:

class RelationsConverter implements JsonConverter<Relation, T> {
  const RelationsConverter();

  @override
  Relation fromJson(K json) => ...

  @override
  T toJson(Skill data) => ...
}

@freezed
class MyModel with _$MyModel {
  const factory MyModel({
    String name,
    @RelationsConverter() List<Relation>? relations,
  }) = _MyModel;

  factory MyModel.fromJson(Map<String, Object?> json) => _$MyModelFromJson(json);
}

The problem here is that while jsonFrom is passed a Map, toJson returns a String:

Relation fromJson(Map<String, dynamic> json) => Relation.fromJson(json);

String toJson(Relation relation) => relation.id;

However I need to pass a specific type for both to-and fromJson to JsonConverter: class RelationsConverter implements JsonConverter<Relation, [Map or String]>

I neither can omit fromJson (which I'd like to) because the interface forces me to implement both methods.

How can I solve this? I I could get around my custom transforming if the incoming data and solve this in a converter, this would also be nice.

1

There are 1 best solutions below

0
On

it's a little late to answer the question but since i encountered a similar issue, i found a workaround to it using JsonConverter

for example, if you are converting a variable someBoolean bool -> string when sending data to the api and the api sends someBoolean as bool back then you would have bool->string while sending and bool->bool while receiving.

the json converter would look something like this:

class BoolConverter implements JsonConverter<bool, dynamic> {
  const BoolConverter ();

  // use dynamic to determine runtime type
  @override
  bool fromJson(dynamic json) {
    if (json == null) return false;
    if (json is bool) return json;
    return json is String ? json.toBoolean() : false;
  }

  @override
  String toJson(bool value) {
    return value ? 'true' : 'false';
  }

}