skip to Main Content

I have an API which sends me a type :

type Response = {
  id: string;
  values: Stringified<number[]>, // string
}

where type Stringified<T> = string; and is what you would get by using jsonEncode.

I want to create a Built class that matches the response architecture. This is what I have for now:

@SerializersFor([
  MyResponse,
])
final Serializers _serializers = (_$_serializers.toBuilder()
      ..addPlugin(StandardJsonPlugin())
    .build();

abstract class MyResponse implements Built<MyResponse, MyResponseBuilder> {
  factory MyResponse([void Function(MyResponseBuilder) updates]) = _$MyResponse;

  MyResponse._();

  Map<String, dynamic> toJson() {
    return _serializers.serializeWith(MyResponse.serializer, this)! as Map<String, dynamic>;
  }

  static MyResponse fromJson(Map<String, dynamic> json) {
    return _serializers.deserializeWith(MyResponse.serializer, json)!;
  }

  static Serializer<MyResponse> get serializer => _$myResponseSerializer;

  String get id;

  List<int> get values;
}

This would work if I was receiving/sending data like :

{ "id": "id", "values": [0, 1, 2] }

But the data I receive or need to send is:

{ "id": "id", "values": "[0, 1, 2]" }

How can I override the toJson / fromJson of values or how can I add a pipe/how so I can use jsonEncode/jsonDecode when exporting to/importing from a JSON?

2

Answers


  1. Chosen as BEST ANSWER

    In the end, I managed to do it with a SerializerPlugin :

    Here is a more complete example with 2 models:

    type Model1 {
      boolean: boolean;
      integer: number;
    }
    
    type Model2 = {
      char: string;
      list: Stringified<Model1[]>;
    }
    

    where type Stringified<T> = string; and is what you would get by using jsonEncode.

    I created those built value classes:

    // model_1.dart
    
    import 'package:built_value/built_value.dart';
    import 'package:built_value/serializer.dart';
    import 'package:built_value/standard_json_plugin.dart';
    
    part 'model_1.g.dart';
    
    @SerializersFor([
      Model1,
    ])
    final Serializers _serializers =
        (_$_serializers.toBuilder()..addPlugin(StandardJsonPlugin())).build();
    
    abstract class Model1 implements Built<Model1, Model1Builder> {
      Model1._();
      factory Model1([void Function(Model1Builder) updates]) = _$Model1;
    
      Map<String, dynamic> toJson() {
        return _serializers.serializeWith(Model1.serializer, this)
            as Map<String, dynamic>;
      }
    
      static Model1 fromJson(Map<String, dynamic> json) {
        return _serializers.deserializeWith(Model1.serializer, json)!;
      }
    
      static Serializer<Model1> get serializer => _$model1Serializer;
    
      bool get boolean;
    
      int get integer;
    }
    
    // model_2.dart
    
    import 'dart:convert';
    
    import 'package:built_collection/built_collection.dart';
    import 'package:built_value/built_value.dart';
    import 'package:built_value/serializer.dart';
    import 'package:built_value/standard_json_plugin.dart';
    import 'package:flutter_app_stable/model_1.dart';
    import 'package:flutter_app_stable/my_plugin.dart';
    
    part 'model_2.g.dart';
    
    @SerializersFor([
      Model2,
      Model1,
    ])
    final Serializers _serializers = (_$_serializers.toBuilder()
          ..addPlugin(StandardJsonPlugin())
          ..addPlugin(MyPlugin()) // <- Add the plugin here.
          ..addBuilderFactory(
            const FullType(BuiltList, [FullType(Model1)]),
            ListBuilder<Model1>.new,
          ))
        .build();
    
    abstract class Model2 implements Built<Model2, Model2Builder> {
      Model2._();
      factory Model2([void Function(Model2Builder) updates]) = _$Model2;
    
      Map<String, dynamic> toJson() {
        return _serializers.serializeWith(Model2.serializer, this)
            as Map<String, dynamic>;
      }
    
      static Model2 fromJson(Map<String, dynamic> json) {
        return _serializers.deserializeWith(Model2.serializer, json)!;
      }
    
      static Serializer<Model2> get serializer => _$model2Serializer;
    
      String get char;
    
      BuiltList<Model1> get list;
    }
    
    // my_plugin.dart
    
    class MyPlugin extends SerializerPlugin {
      @override
      Object? afterDeserialize(Object? object, FullType specifiedType) {
        return object;
      }
    
      @override
      Object? afterSerialize(Object? object, FullType specifiedType) {
        if (specifiedType.root == BuiltList &&
            specifiedType.parameters.length == 1 &&
            specifiedType.parameters.first.root == Model1) {
          return jsonEncode(object);
        } else {
          return object;
        }
      }
    
      @override
      Object? beforeDeserialize(Object? object, FullType specifiedType) {
        if (specifiedType.root == BuiltList &&
            specifiedType.parameters.length == 1 &&
            specifiedType.parameters.first.root == Model1) {
          return jsonDecode(object as String);
        } else {
          return object;
        }
      }
    
      @override
      Object? beforeSerialize(Object? object, FullType specifiedType) {
        return object;
      }
    }
    

    Notice the class MyPlugin extends SerializerPlugin in model_2.dart. I add this plugin to the Model2 serializer.

    It

    • Overrides afterSerialize that applies jsonEncode to the serialized list of serialized model1
    • Overrides beforeDeserialize that applies jsonDecode to the deserialized stringified list of serialized model1

  2. Im not sure this fit to your need. I ussually modify my toJson() method by changet its value.

    for example: in my model I use @JsonSerializable which will generated default toJson.to customize some keys, i do like below:

    Map<String, dynamic> toJson() {
        final Map<String, dynamic> data = _$MyModelToJson(this);
        data['status'] = status?.id;
        return data;
      }
    

    maybe you can use similar pattern.

    Map<String, dynamic> toJson() {
       final  data = _serializers.serializeWith(MyResponse.serializer, this)! as Map<String, dynamic>;
    
       data['values'] = "${data['values']}";
    
       return data;
      }
    
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search