k-paxian / dart-json-mapper

Serialize / Deserialize Dart Objects to / from JSON
https://pub.dev/packages/dart_json_mapper
Other
399 stars 33 forks source link
annotation annotations code-generator dart dart-json-mapper dartlang deserialization flutter fromjson json reflection serialization tojson

SWUbanner Build Status pub package Pub Points Popularity

This package allows programmers to annotate Dart objects in order to Serialize / Deserialize them to / from JSON.

Why?

Dart classes reflection mechanism is based on reflectable library. This means "extended types information" is auto-generated out of existing Dart program guided by the annotated classes only, as the result types information is accessible at runtime, at a reduced cost.

Basic setup

Please add the following dependencies to your pubspec.yaml:

dependencies:
  dart_json_mapper:
dev_dependencies:
  build_runner:

Say, you have a dart program main.dart having some classes intended to be traveling to JSON and back.

lib/main.dart

import 'package:dart_json_mapper/dart_json_mapper.dart' show JsonMapper, jsonSerializable, JsonProperty;

import 'main.mapper.g.dart' show initializeJsonMapper;

@jsonSerializable // This annotation let instances of MyData travel to/from JSON
class MyData {
  int a = 123;

  @JsonProperty(ignore: true)
  bool b;

  @JsonProperty(name: 'd')
  String c;

  MyData(this.a, this.b, this.c);
}

void main() {
  initializeJsonMapper();

  print(JsonMapper.serialize(MyData(456, true, "yes")));
}

output:

{ 
  "a": 456,
  "d": "yes"
}

Go ahead and create / update build.yaml file in your project root directory with the following snippet:

targets:
  $default:
    builders:
      dart_json_mapper:
        generate_for:
        # here should be listed entry point files having 'void main()' function
          - lib/main.dart

      # This part is needed to tell original reflectable builder to stay away
      # it overrides default options for reflectable builder to an **empty** set of files
      reflectable:
        generate_for:
          - no/files

Now run the code generation step with the root of your package as the current directory:

dart run build_runner build --delete-conflicting-outputs

You'll need to re-run code generation each time you are making changes to lib/main.dart So for development time, use watch like this

dart run build_runner watch --delete-conflicting-outputs

Each time you modify your project code, all *.mapper.g.dart files will be updated as well.

Format DateTime / num types

In order to format DateTime or num instance as a JSON string, it is possible to provide intl based formatting patterns.

DateTime

@JsonProperty(converterParams: {'format': 'MM-dd-yyyy H:m:s'})
DateTime lastPromotionDate = DateTime(2008, 05, 13, 22, 33, 44);

@JsonProperty(converterParams: {'format': 'MM/dd/yyyy'})
DateTime hireDate = DateTime(2003, 02, 28);

output:

{
"lastPromotionDate": "05-13-2008 22:33:44",
"hireDate": "02/28/2003"
}

num

@JsonProperty(converterParams: {'format': '##.##'})
num salary = 1200000.246;

output:

{
"salary": "1200000.25"
}

As well, it is possible to utilize converterParams map to provide custom parameters to your custom converters.

Get or Set fields

When relying on Dart getters / setters, no need to annotate them. But when you have custom getter / setter methods, you should provide annotations for them.

@jsonSerializable
class AllPrivateFields {
  String? _name;
  String? _lastName;

  set name(dynamic value) {
    _name = value;
  }

  String? get name => _name;

  @JsonProperty(name: 'lastName')
  void setLastName(dynamic value) {
    _lastName = value;
  }

  @JsonProperty(name: 'lastName')
  String? getLastName() => _lastName;
}

// given
final json = '''{"name":"Bob","lastName":"Marley"}''';

// when
final instance = JsonMapper.deserialize<AllPrivateFields>(json);

// then
expect(instance.name, 'Bob');
expect(instance.getLastName(), 'Marley');

// when
final targetJson = JsonMapper.serialize(instance, SerializationOptions(indent: ''));

// then
expect(targetJson, json);

Example with immutable class

@jsonSerializable
enum Color { red, blue, green, brown, yellow, black, white }

@jsonSerializable
class Car {
    @JsonProperty(name: 'modelName')
    String model;

    Color color;

    @JsonProperty(ignore: true)
    Car replacement;

    Car(this.model, this.color);
}

@jsonSerializable
class Immutable {
    final int id;
    final String name;
    final Car car;

    const Immutable(this.id, this.name, this.car);
}

print(
  JsonMapper.serialize(
    Immutable(1, 'Bob', Car('Audi', Color.green))
  )
);

output:

{
 "id": 1,
 "name": "Bob",
 "car": {
  "modelName": "Audi",
  "color": "green"
 }
}

Constructor parameters

Sometimes you don't really care or don't want to store some json property as a dedicated class field, but instead, you would like to use it's value in constructor to calculate other class properties. This way you don't have a convenience to annotate a class field, but you could utilize constructor parameter for that.

With the input JSON like this:

{"LogistikTeileInOrdnung":"true"}

You could potentially have a class like this:

@jsonSerializable
class BusinessObject {
  final bool logisticsChecked;
  final bool logisticsOK;

  BusinessObject()
      : logisticsChecked = false,
        logisticsOK = true;

  @jsonConstructor
  BusinessObject.fromJson(
      @JsonProperty(name: 'LogistikTeileInOrdnung') String processed)
      : logisticsChecked = processed != null && processed != 'null',
        logisticsOK = processed == 'true';
}

Unmapped properties

If you are looking for an alternative to Java Jackson @JsonAnySetter / @JsonAnyGetter It is possible to configure the same scenario as follows:

@jsonSerializable
class UnmappedProperties {
  String name;

  Map<String, dynamic> _extraPropsMap = {};

  @jsonProperty
  void unmappedSet(String name, dynamic value) {
    _extraPropsMap[name] = value;
  }

  @jsonProperty
  Map<String, dynamic> unmappedGet() {
    return _extraPropsMap;
  }
}

// given
final json = '''{"name":"Bob","extra1":1,"extra2":"xxx"}''';

// when
final instance = JsonMapper.deserialize<UnmappedProperties>(json);

// then
expect(instance.name, 'Bob');
expect(instance._extraPropsMap['name'], null);
expect(instance._extraPropsMap['extra1'], 1);
expect(instance._extraPropsMap['extra2'], 'xxx');

Iterable types

Since Dart language has no possibility to create typed iterables dynamically, it's a bit of a challenge to create exact typed lists/sets/etc via reflection approach. Those types has to be declared explicitly.

For example List() will produce List<dynamic> type which can't be directly set to the concrete target field List<Car> for instance. So obvious workaround will be to cast List<dynamic> => List<Car>, which can be performed as List<dynamic>().cast<Car>().

Basic iterable based generics using Dart built-in types like List<num>, List<String>, List<bool>, List<DateTime>, Set<num>, Set<String>, Set<bool>, Set<DateTime>, etc. supported out of the box.

In order to do so, we'll use Value Decorator Functions inspired by Decorator pattern.

To solve this we have a few options:

Provide value decorator functions manually

Rely on builder to generate global adapter having value decorator functions automatically

Builder will scan project code during build pass and will generate value decorator functions for all annotated public classes in advance.

For custom iterable types like List<Car> / Set<Car> we don't have to provide value decorators as showed in a code snippet below, thanks to the Builder

final json = '[{"modelName": "Audi", "color": "green"}]';
final myCarsList = JsonMapper.deserialize<List<Car>>(json);
final myCarsSet = JsonMapper.deserialize<Set<Car>>(json);

For custom iterable types like HashSet<Car> / UnmodifiableListView<Car> we should configure Builder to support that.

OR an easy case

When you are able to pre-initialize your Iterables with an empty instance, like on example below, you don't need to mess around with value decorators.

@jsonSerializable
class Item {}

@jsonSerializable
class IterablesContainer {
  List<Item> list = [];
  Set<Item> set = {};
}

// given
final json = '''{"list":[{}, {}],"set":[{}, {}]}''';

// when
final target = JsonMapper.deserialize<IterablesContainer>(json);

// then
expect(target.list, TypeMatcher<List<Item>>());
expect(target.list.first, TypeMatcher<Item>());
expect(target.list.length, 2);

expect(target.set, TypeMatcher<Set<Item>>());
expect(target.set.first, TypeMatcher<Item>());
expect(target.set.length, 2);

List of Lists of Lists ...

Using value decorators, it's possible to configure nested lists of virtually any depth.

@jsonSerializable
class Item {}

@jsonSerializable
@Json(valueDecorators: ListOfLists.valueDecorators)
class ListOfLists {
  static Map<Type, ValueDecoratorFunction> valueDecorators() =>
      {
        typeOf<List<List<Item>>>(): (value) => value.cast<List<Item>>(),
        typeOf<List<Item>>(): (value) => value.cast<Item>()
      };

  List<List<Item>>? lists;
}

// given
final json = '''{
 "lists": [
   [{}, {}],
   [{}, {}, {}]
 ]
}''';

// when
final target = JsonMapper.deserialize<ListOfLists>(json)!;

// then
expect(target.lists?.length, 2);
expect(target.lists?.first.length, 2);
expect(target.lists?.last.length, 3);
expect(target.lists?.first.first, TypeMatcher<Item>());
expect(target.lists?.last.first, TypeMatcher<Item>());

Enum types

Enum construction in Dart has a specific meaning, and has to be treated accordingly.

Generally, we always have to bear in mind following cases around Enums:

There are few enum converters provided out of the box:

Default converter for all enums is enumConverterShort

In case we would like to make a switch globally to the different one, or even custom converter for all enums

// lib/main.dart
void main() {
  initializeJsonMapper(adapters: [
   JsonMapperAdapter(converters: {Enum: enumConverter})
  ]);
}

Enums having String / num values

What are the options if you would like to serialize / deserialize Enum values as custom values?

OR

While registering standalone enums via adapter it is possible to specify value mapping for each enum, alongside defaultValue which will be used during deserialization of unknown Enum values.

import 'package:some_package' show ThirdPartyEnum, ThirdPartyEnum2, ThirdPartyEnum3;

JsonMapper().useAdapter(
    JsonMapperAdapter(enumValues: {
        ThirdPartyEnum: ThirdPartyEnum.values,
       ThirdPartyEnum2: EnumDescriptor(
                            values: ThirdPartyEnum2.values,
                           mapping: <ThirdPartyEnum2, String>{
                                      ThirdPartyEnum2.A: 'AAA',
                                      ThirdPartyEnum2.B: 'BBB',
                                      ThirdPartyEnum2.C: 'CCC'
                                    }
                        ),
       ThirdPartyEnum3: EnumDescriptor(
                            values: ThirdPartyEnum3.values,
                      defaultValue: ThirdPartyEnum3.A,
                           mapping: <ThirdPartyEnum3, num>{
                                      ThirdPartyEnum3.A: -1.2,
                                      ThirdPartyEnum3.B: 2323,
                                      ThirdPartyEnum3.C: 1.2344
                                    }
                        )
    })
);

So this way, you'll still operate on classic / pure Dart enums and with all that sending & receiving them as mapped values. After registering those enums once, no matter where in the code you'll use them later they will be handled according to the configuration given w/o annotating them beforehand.

Inherited classes derived from abstract / base class

Please use complementary @Json(discriminatorProperty: 'type') annotation for abstract or base class to specify which class field(type in this snippet below) will be used to store a value for distinguishing concrete subclass type.

Please use complementary @Json(discriminatorValue: <your property value>) annotation for subclasses derived from abstract or base class. If this annotation omitted, class name will be used as discriminatorValue

This ensures, that dart-json-mapper will be able to reconstruct the object with the proper type during deserialization process.

@jsonSerializable
enum BusinessType { Private, Public }

@jsonSerializable
@Json(discriminatorProperty: 'type')
abstract class Business {
  String? name;
  BusinessType? type;
}

@jsonSerializable
@Json(discriminatorValue: BusinessType.Private)
class Hotel extends Business {
  int stars;

  Hotel(this.stars);
}

@jsonSerializable
@Json(discriminatorValue: BusinessType.Public)
class Startup extends Business {
  int userCount;

  Startup(this.userCount);
}

@jsonSerializable
class Stakeholder {
  String fullName;
  List<Business> businesses = [];

  Stakeholder(this.fullName, this.businesses);
}

// given
final jack = Stakeholder("Jack", [Startup(10), Hotel(4)]);

// when
final String json = JsonMapper.serialize(jack);
final Stakeholder target = JsonMapper.deserialize(json);

// then
expect(target.businesses[0], TypeMatcher<Startup>());
expect(target.businesses[1], TypeMatcher<Hotel>());

Classes enhanced with Mixins derived from abstract class

Similar configuration as above also works well for class mixins

@Json(discriminatorProperty: 'type')
@jsonSerializable
abstract class A {}

@jsonSerializable
mixin B on A {}

@jsonSerializable
class C extends A with B {}

@jsonSerializable
class MixinContainer {
  final Set<int> ints;
  final B b;

  const MixinContainer(this.ints, this.b);
}

// given
final json = r'''{"ints":[1,2,3],"b":{"type":"C"}}''';
final instance = MixinContainer({1, 2, 3}, C());

// when
final targetJson = JsonMapper.serialize(instance);
final target = JsonMapper.deserialize<MixinContainer>(targetJson);

// then
expect(targetJson, json);
expect(target, TypeMatcher<MixinContainer>());
expect(target.b, TypeMatcher<C>());

Serialization template

In case you already have an instance of huge JSON Map object and portion of it needs to be surgically updated, then you can pass your Map<String, dynamic> instance as a template parameter for SerializationOptions

// given
final template = {'a': 'a', 'b': true};

// when
final json = JsonMapper.serialize(Car('Tesla S3', Color.black),
  SerializationOptions(indent: '', template: template));

// then
expect(json,
  '''{"a":"a","b":true,"modelName":"Tesla S3","color":"black"}''');

Deserialization template

In case you need to deserialize specific Map<K, V> type then you can pass typed instance of it as a template parameter for DeserializationOptions.

Since typed Map<K, V> instance cannot be created dynamically due to Dart language nature, so you are providing ready made instance to use for deserialization output.

// given
final json = '{"black":1,"blue":2}';

// when
final target = JsonMapper.deserialize(
          json, DeserializationOptions(template: <Color, int>{}));

// then
expect(target, TypeMatcher<Map<Color, int>>());
expect(target.containsKey(Color.black), true);
expect(target.containsKey(Color.blue), true);
expect(target[Color.black], 1);
expect(target[Color.blue], 2);

Name casing styles [Pascal, Kebab, Snake, SnakeAllCaps]

Assuming your Dart code is following Camel case style, but that is not always true for JSON models, they could follow one of those popular - Pascal, Kebab, Snake, SnakeAllCaps styles, right?

That's why we need a smart way to manage that, instead of hand coding each property using @JsonProperty(name: ...) it is possible to pass CaseStyle parameter to serialization / deserialization methods OR specify this preference on a class level using @Json(caseStyle: CaseStyle.kebab).

@jsonSerializable
enum Color { red, blue, gray, grayMetallic, green, brown, yellow, black, white }

@jsonSerializable
@Json(caseStyle: CaseStyle.kebab)
class NameCaseObject {
  String mainTitle;
  bool hasMainProperty;
  Color primaryColor;

  NameCaseObject({
      this.mainTitle,
      this.hasMainProperty,
      this.primaryColor = Color.grayMetallic});
}

/// Serialization

// given
final instance = NameCaseObject(mainTitle: 'title', hasMainProperty: true);
// when
final json = JsonMapper.serialize(instance, SerializationOptions(indent: ''));
// then
expect(json, '''{"main-title":"title","has-main-property":true,"primary-color":"gray-metallic"}''');

/// Deserialization

// given
final json = '''{"main-title":"title","has-main-property":true,"primary-color":"gray-metallic"}''';
// when
final instance = JsonMapper.deserialize<NameCaseObject>(json);
// then
expect(instance.mainTitle, 'title');
expect(instance.hasMainProperty, true);
expect(instance.primaryColor, Color.grayMetallic);

Nesting configuration

In case if you need to operate on particular portions of huge JSON object and you don't have a true desire to reconstruct the same deep nested JSON objects hierarchy with corresponding Dart classes. This section is for you!

Say, you have a json similar to this one

{
  "root": {
    "foo": {
      "bar": {
        "baz": {
          "items": [
            "a",
            "b",
            "c"
          ]
        }
      }
    }
  }
}          

And with code similar to this one

@jsonSerializable
@Json(name: 'root/foo/bar')
class BarObject {
  @JsonProperty(name: 'baz/items')
  List<String> items;

  BarObject({this.items});
}

// when
final instance = JsonMapper.deserialize<BarObject>(json);

// then
expect(instance.items.length, 3);
expect(instance.items, ['a', 'b', 'c']);

you'll have it done nice and quick.

@Json(name: 'root/foo/bar') provides a root nesting for the entire annotated class, this means all class fields will be nested under this 'root/foo/bar' path in Json.

@JsonProperty(name: 'baz/items') provides a field nesting relative to the class root nesting

name is compliant with RFC 6901 JSON pointer

Relative path reference to parent field from nested object "../id"

When it's handy to refer to the parent fields values, it's possible to use path like notation "../"

[
  {"id":1,"name":"category1","products":[
         {"id":3629,"name":"Apple","features":[{"id":9,"name":"Red Color"}]},
         {"id":5674,"name":"Banana"}]},
  {"id":2,"name":"category2","products":[
         {"id":7834,"name":"Car"},
         {"id":2386,"name":"Truck"}
   ]}
]
@jsonSerializable
class Feature {
  @JsonProperty(name: '../../id')
  num categoryId;

  @JsonProperty(name: '../id')
  num productId;

  num id;
  String name;

  Feature({this.name, this.id});
}

@jsonSerializable
class Product {
  @JsonProperty(name: '../id')
  num categoryId;

  num id;
  String name;

  @JsonProperty(ignoreIfNull: true)
  List<Feature> features;

  Product({this.name, this.id, this.features});
}

@jsonSerializable
class ProductCategory {
  num id;
  String name;
  List<Product> products;

  ProductCategory({this.id, this.name, this.products});
}

Relative path reference to parent itself from nested object ".."

In some cases objects need to interact with their (owning) parent object. The easiest pattern is to add a referencing field for the parent which is initialized during construction of the child object. The path notation ".." supports this pattern:

@jsonSerializable
class Parent {
  String? lastName;
  List<Child> children = [];
}

@jsonSerializable
class Child {
  String? firstName;

  @JsonProperty(name: '..')
  Parent parent;

  Child(this.parent);
}

You are now able to deserialize the following structure:

{
  "lastName": "Doe",
  "children": [
    {"firstName": "Eve"},
    {"firstName": "Bob"},
    {"firstName": "Alice"}
]}

and each Child object will have a reference on it's parent. And this parent field will not leak out to the serialized JSON object

Value injection

Sometimes you have to inject certain values residing outside of a JSON string into the target deserialized object. Using the JsonProperty.inject flag, one may do so.

class Outside {}

@jsonSerializable
class Inside {
  String? foo;

  @JsonProperty(name: 'data/instance', inject: true)
  Outside? outside;
}

You may then inject the values in the deserialize method:

{
  "foo": "Bar"
}
Outside outsideInstance = Outside();
final target = JsonMapper.deserialize<Inside>(json,
  DeserializationOptions(injectableValues: {'data': {'instance': outsideInstance}})!;

Name aliases configuration

For cases when aliasing technique is desired, it's possible to optionally merge / route many json properties into one class field. First name from the list is treated as primary i.e. used for serialization direction. The rest of items are treated as aliases joined by the ?? operation.

@jsonSerializable
class FieldAliasObject {
  // same as => alias ?? fullName ?? name
  @JsonProperty(name: ['alias', 'fullName', 'name'])
  final String name;

  const FieldAliasObject({
    this.name,
  });
}

Schemes

Scheme - is a set of annotations associated with common scheme id. This enables the possibility to map a single Dart class to many different JSON structures.

This approach usually useful for distinguishing [DEV, PROD, TEST, ...] environments, w/o producing separate Dart classes for each environment.

enum Scheme { A, B }

@jsonSerializable
@Json(name: 'default')
@Json(name: '_', scheme: Scheme.B)
@Json(name: 'root', scheme: Scheme.A)
class Object {
  @JsonProperty(name: 'title_test', scheme: Scheme.B)
  String title;

  Object(this.title);
}

// given
final instance = Object('Scheme A');
// when
final json = JsonMapper.serialize(instance, SerializationOptions(indent: '', scheme: Scheme.A));
// then
expect(json, '''{"root":{"title":"Scheme A"}}''');

// given
final instance = Object('Scheme B');
// when
final json = JsonMapper.serialize(instance, SerializationOptions(indent: '', scheme: Scheme.B));
// then
expect(json, '''{"_":{"title_test":"Scheme B"}}''');

// given
final instance = Object('No Scheme');
// when
final json = JsonMapper.serialize(instance, SerializationOptions(indent: ''));
// then
expect(json, '''{"default":{"title":"No Scheme"}}''');

Objects flattening

Consider a paginated API which returns a page of results along with pagination metadata that identifies how many results were requested, how far into the total set of results we are looking at, and how many results exist in total. If we are paging through a total of 1053 results 100 at a time, the third page may look like this:

{
  "limit": 100,
  "offset": 200,
  "total": 1053,
  "users": [
    {"id": "49824073-979f-4814-be10-5ea416ee1c2f", "username": "john_doe"},
    ...
  ]
}

This same scheme with limit and offset and total fields may be shared across lots of different API queries. For example we may want paginated results when querying for users, for issues, for projects, etc.

In this case it can be convenient to factor the common pagination metadata fields into a reusable Pagination shared class that can be flattened & blended into each API response object.

@jsonSerializable
class Pagination {
  num? limit;
  num? offset;
  num? total;
}

@jsonSerializable
class UsersPage {
  @JsonProperty(flatten: true)
  Pagination? pagination;

  List<User>? users;
}

If it's desired to define common prefix for flattened fields @JsonProperty.name attribute could be utilized for that alongside with flatten: true attribute.

Case style could be defined as usual, on a class level @Json(caseStyle: CaseStyle.snake) and/or global scope with DeserializationOptions(caseStyle: CaseStyle.kebab) and SerializationOptions(caseStyle: CaseStyle.kebab) If omitted, CaseStyle.camel is used by default.

@jsonSerializable
class Pagination {
  num? limit;
  num? offset;
  num? total;
}

@jsonSerializable
@Json(caseStyle: CaseStyle.snake)
class UsersPage {
  @JsonProperty(name: 'pagination', flatten: true)
  Pagination? pagination;

  List<User>? users;
}

This will output

{
  "pagination_limit": 100,
  "pagination_offset": 200,
  "pagination_total": 1053,
  "users": [
    {"id": "49824073-979f-4814-be10-5ea416ee1c2f", "username": "john_doe"},
    ...
  ]
}

Objects cloning

If you are wondering how to deep-clone Dart Objects, or even considering using libraries like Freezed to accomplish that, then this section probably will be useful for you

// given
final car = Car('Tesla S3', Color.black);

// when
final cloneCar = JsonMapper.copy(car);

// then
expect(cloneCar == car, false);
expect(cloneCar.color == car.color, true);
expect(cloneCar.model == car.model, true);

Or if you would like to override some properties for the clonned object instance

// given
final car = Car('Tesla S3', Color.black);

// when
final cloneCar = JsonMapper.copyWith(car, {'color': 'blue'}); // overriding Black by Blue

// then
expect(cloneCar == car, false);
expect(cloneCar.color, Color.blue);
expect(cloneCar.model, car.model);

Custom types

For the very custom types, specific ones, or doesn't currently supported by this library, you can provide your own custom Converter class per each custom runtimeType.

/// Abstract class for custom converters implementations
abstract class ICustomConverter<T> {
  dynamic toJSON(T object, SerializationContext context);
  T fromJSON(dynamic jsonValue, DeserializationContext context);
}

All you need to get going with this, is to implement this abstract class

class CustomStringConverter implements ICustomConverter<String> {
  const CustomStringConverter() : super();

  @override
  String fromJSON(dynamic jsonValue, DeserializationContext context) {
    return jsonValue;
  }

  @override
  dynamic toJSON(String object, SerializationContext context) {
    return '_${object}_';
  }
}

And register it afterwards, if you want to have it applied for all occurrences of specified type

JsonMapper().useAdapter(JsonMapperAdapter(
  converters: {
    String: CustomStringConverter()
  })
);

OR use it individually on selected class fields, via @JsonProperty annotation

@JsonProperty(converter: CustomStringConverter())
String title;

Annotations

Builder

This library introduces own builder used to pre-build Default adapter for your application code. Technically, provided builder wraps the reflectable builder output and adds a bit more generated code to it.

Builder can be configured using build.yaml file at the root of your project.

targets:
  $default:
    builders:
      # This part configures dart_json_mapper builder
      dart_json_mapper:
        options:
          iterables: List, Set, HashSet, UnmodifiableListView
        generate_for:
          - example/**.dart
          - test/_test.dart

      # This part is needed to tell original reflectable builder to stay away
      # it overrides default options for reflectable builder to an **empty** set of files
      reflectable:
        generate_for:
          - no/files

Primary mission for the builder at this point is to generate Iterables support for your custom classes.

Options:

iterables: List, Set, HashSet, UnmodifiableListView

This option if omitted defaults to List, Set is used to configure a list of iterables you would like to be supported for you out of the box. For example you have a Car class in your app and would like to have List<Car> and Set<Car> support for deserialization, then you could omit this option.

And when you would like to have a deserialization support for other iterables like HashSet<Car>, UnmodifiableListView<Car> you could add them to the list for this option.

Known limitations

Complementary adapter libraries

If you want a seamless integration with popular use cases, feel free to pick an existing adapter or create one for your use case and make a PR to this repo.

Adapter - is a library which contains a bundle of pre-configured:

For example, you would like to refer to Color type from Flutter in your model class.

You can easily mix and combine several adapters using following one-liner:

JsonMapper()
   .useAdapter(fixnumAdapter)
   .useAdapter(flutterAdapter)
   .useAdapter(mobXAdapter)
   .useAdapter(builtAdapter)
   .info(); // print out a list of used adapters to console