Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/k-paxian/dart-json-mapper
Serialize / Deserialize Dart Objects to / from JSON
https://github.com/k-paxian/dart-json-mapper
annotation annotations code-generator dart dart-json-mapper dartlang deserialization flutter fromjson json reflection serialization tojson
Last synced: 29 days ago
JSON representation
Serialize / Deserialize Dart Objects to / from JSON
- Host: GitHub
- URL: https://github.com/k-paxian/dart-json-mapper
- Owner: k-paxian
- License: other
- Created: 2018-05-23T20:03:01.000Z (over 6 years ago)
- Default Branch: master
- Last Pushed: 2024-07-17T17:02:09.000Z (4 months ago)
- Last Synced: 2024-09-29T16:03:49.992Z (about 1 month ago)
- Topics: annotation, annotations, code-generator, dart, dart-json-mapper, dartlang, deserialization, flutter, fromjson, json, reflection, serialization, tojson
- Language: Dart
- Homepage: https://pub.dev/packages/dart_json_mapper
- Size: 2.03 MB
- Stars: 400
- Watchers: 6
- Forks: 33
- Open Issues: 11
-
Metadata Files:
- Readme: README.md
- Funding: .github/FUNDING.yml
- License: LICENSE
Awesome Lists containing this project
README
[![SWUbanner](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner2-direct.svg)](https://vshymanskyy.github.io/StandWithUkraine)
[![Build Status][ci-badge]][ci-badge-url]
[![pub package](https://img.shields.io/pub/v/dart_json_mapper.svg)](https://pub.dartlang.org/packages/dart_json_mapper)
[![Pub Points](https://img.shields.io/pub/points/dart_json_mapper)](https://pub.dev/packages/dart_json_mapper/score)
[![Popularity](https://img.shields.io/pub/popularity/dart_json_mapper)](https://pub.dev/packages/dart_json_mapper/score)This package allows programmers to annotate Dart objects in order to
Serialize / Deserialize them to / from JSON.
## Why?* Compatible with **all** target platforms for Dart language, **NO** dependency on `dart:mirrors`, one of the reasons is described [here][1].
* No need to extend your classes from **any** mixins/base/abstract classes to keep code leaner
* No enforced private constructors, do not require any constructors at all for your data-only classes
* No magic `_$` prefixes, No enforced `static` fields in your classes
* Clean and simple setup, transparent and straight-forward usage with **no heavy maintenance**
* Inspired by [json2typescript][4], [serde][5], [gson][6], feature parity with highly popular [Java Jackson][12] and only **4** [annotations](#annotations) to remember to cover all possible use cases.
* **No extra boilerplate**, no messy extra `*.g.dart` files per *each* meaningful file (single root-level file which contains all of the generated code)
* **Complementary adapters** full control over the process when you strive for maximum flexibility.
* `Configuration over code` brings **predictability** to your codebase, while reducing overall amount of code to read / maintain
* Because Serialization/Deserialization is **NOT** a responsibility of your Model classes.Dart classes reflection mechanism is based on [reflectable][3] 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.![](banner.svg)
* [Basic setup](#basic-setup)
* [Annotations](#annotations)
* [Builder](#builder)
* [Known limitations](#known-limitations)
* [Documentation][docs]
* [Configuration use cases](#format-datetime--num-types)
* [Extended classes](#inherited-classes-derived-from-abstract--base-class)
* [Classes with Mixins](#classes-enhanced-with-mixins-derived-from-abstract-class)
* [Immutable classes](#example-with-immutable-class)
* [Get or Set fields](#get-or-set-fields)
* [Constructor parameters](#constructor-parameters)
* [Unmapped properties](#unmapped-properties)
* [DateTime / num types](#format-datetime--num-types)
* [Iterable types](#iterable-types)
* [Value injection](#value-injection)
* [Enum types](#enum-types)
* [Enums having String / num values](#enums-having-string--num-values)
* [Name casing styles](#name-casing-styles-pascal-kebab-snake-snakeallcaps)
* [Serialization template](#serialization-template)
* [Deserialization template](#deserialization-template)
* [Custom types](#custom-types)
* [Nesting](#nesting-configuration)
* [Name aliases](#name-aliases-configuration)
* [Relative path reference to parent field from nested object "../id"](#relative-path-reference-to-parent-field-from-nested-object-id)
* [Relative path reference to parent itself from nested object ".."](#relative-path-reference-to-parent-itself-from-nested-object-)
* [Schemes](#schemes)
* [Objects flattening](#objects-flattening)
* [Objects cloning](#objects-cloning)
* [Adapters](#complementary-adapter-libraries)
* [How to use adapter?](#complementary-adapter-libraries)
* [![pub package](https://img.shields.io/pub/v/dart_json_mapper_built.svg)](https://pub.dartlang.org/packages/dart_json_mapper_built) | [dart_json_mapper_built](adapters/built) | [Built Collection][16]
* [![pub package](https://img.shields.io/pub/v/dart_json_mapper_mobx.svg)](https://pub.dartlang.org/packages/dart_json_mapper_mobx) | [dart_json_mapper_mobx](adapters/mobx) | [MobX][7]
* [![pub package](https://img.shields.io/pub/v/dart_json_mapper_fixnum.svg)](https://pub.dartlang.org/packages/dart_json_mapper_fixnum) | [dart_json_mapper_fixnum](adapters/fixnum) | [Fixnum][8]
* [![pub package](https://img.shields.io/pub/v/dart_json_mapper_flutter.svg)](https://pub.dartlang.org/packages/dart_json_mapper_flutter) | [dart_json_mapper_flutter](adapters/flutter) | [Flutter][11]## Basic setup
Please add the following dependencies to your `pubspec.yaml`:
```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.
- First thing you should do is to put `@jsonSerializable` annotation on each of those classes
- Next step is to auto generate *main.mapper.g.dart* file. And afterwards import that file into *main.dart***lib/main.dart**
```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:
```json
{
"a": 456,
"d": "yes"
}
```Go ahead and create / update `build.yaml` file in your project root directory with the following snippet:
```yaml
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:
```shell
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```shell
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.
- Next step is to add `*.mapper.g.dart` to your .gitignore
- And this is it, you are all set and ready to go. Happy coding!## Format DateTime / num types
In order to format `DateTime` or `num` instance as a JSON string, it is possible to
provide [intl][2] based formatting patterns.**DateTime**
```dart
@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:
```json
{
"lastPromotionDate": "05-13-2008 22:33:44",
"hireDate": "02/28/2003"
}
```**num**
```dart
@JsonProperty(converterParams: {'format': '##.##'})
num salary = 1200000.246;
```
output:
```json
{
"salary": "1200000.25"
}
```As well, it is possible to utilize `converterParams` map to provide custom
parameters to your [custom converters](#custom-types).## 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.```dart
@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(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
```dart
@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:
```json
{
"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:
```json
{"LogistikTeileInOrdnung":"true"}
```You could potentially have a class like this:
```dart
@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:```dart
@jsonSerializable
class UnmappedProperties {
String name;Map _extraPropsMap = {};
@jsonProperty
void unmappedSet(String name, dynamic value) {
_extraPropsMap[name] = value;
}@jsonProperty
Map unmappedGet() {
return _extraPropsMap;
}
}// given
final json = '''{"name":"Bob","extra1":1,"extra2":"xxx"}''';// when
final instance = JsonMapper.deserialize(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` type which can't be directly set to the concrete
target field `List` for instance. So obvious workaround will be to cast
`List => List`, which can be performed as `List().cast()`.Basic iterable based generics using Dart built-in types like `List, List, List,
List, Set, Set, Set, Set, 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
* As a global adapter
```dart
JsonMapper().useAdapter(JsonMapperAdapter(
valueDecorators: {
typeOf>(): (value) => value.cast(),
typeOf>(): (value) => value.cast()
})
);
final json = '[{"modelName": "Audi", "color": "green"}]';
final myCarsList = JsonMapper.deserialize>(json);
final myCarsSet = JsonMapper.deserialize>(json);
```* As an class inline code
```dart
@jsonSerializable
@Json(valueDecorators: CarsContainer.valueDecorators)
class CarsContainer {
static Map valueDecorators() =>
{
typeOf>(): (value) => value.cast(),
typeOf>(): (value) => value.cast()
};
List myCarsList;
Set myCarsSet;
}
```### 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 / Set` we **don't** have to provide value decorators
as showed in a code snippet below, thanks to the [Builder](#builder)```dart
final json = '[{"modelName": "Audi", "color": "green"}]';
final myCarsList = JsonMapper.deserialize>(json);
final myCarsSet = JsonMapper.deserialize>(json);
```For custom iterable types like `HashSet / UnmodifiableListView` we should configure
[Builder](#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.```dart
@jsonSerializable
class Item {}@jsonSerializable
class IterablesContainer {
List list = [];
Set set = {};
}// given
final json = '''{"list":[{}, {}],"set":[{}, {}]}''';// when
final target = JsonMapper.deserialize(json);// then
expect(target.list, TypeMatcher>());
expect(target.list.first, TypeMatcher());
expect(target.list.length, 2);expect(target.set, TypeMatcher>());
expect(target.set.first, TypeMatcher());
expect(target.set.length, 2);
```### List of Lists of Lists ...
Using value decorators, it's possible to configure nested lists of
virtually any depth.```dart
@jsonSerializable
class Item {}@jsonSerializable
@Json(valueDecorators: ListOfLists.valueDecorators)
class ListOfLists {
static Map valueDecorators() =>
{
typeOf>>(): (value) => value.cast>(),
typeOf>(): (value) => value.cast()
};
List>? lists;
}// given
final json = '''{
"lists": [
[{}, {}],
[{}, {}, {}]
]
}''';// when
final target = JsonMapper.deserialize(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());
expect(target.lists?.last.first, TypeMatcher());
```## 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:
* Your own Enums declared as part of your program code, thus they **can** be annotated.
So whenever possible, you should annotate your Enum declarations as follows
```dart
@jsonSerializable
enum Color { red, blue, green, brown, yellow, black, white }
```* Standalone Enums from third party packages, they **can not** be annotated.
So you should register those enums via adapter as follows:
```dart
import 'package:some_package' show ThirdPartyEnum, ThirdPartyEnum2;
JsonMapper().useAdapter(
JsonMapperAdapter(enumValues: {
ThirdPartyEnum: ThirdPartyEnum.values,
ThirdPartyEnum2: ThirdPartyEnum2.values
})
);
```
Enum`.values` refers to a list of all possible enum values, it's a handy built in capability of all
enum based types. Without providing all values it's not possible to traverse it's values properly.There are few enum converters provided out of the box:
* `enumConverterShort` produces values like: ["red", "blue", "green"], unless custom value mappings provided
* `enumConverter` produces values like: ["Color.red", "Color.blue", "Color.green"]
* `enumConverterNumeric` produces values like: [0, 1, 2]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
```dart
// 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?
* Wrap each enum as a class, to reflect it's values as something different
* Use other libraries for sealed classes like [SuperEnum][14], [Freezed][15]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.```dart
import 'package:some_package' show ThirdPartyEnum, ThirdPartyEnum2, ThirdPartyEnum3;JsonMapper().useAdapter(
JsonMapperAdapter(enumValues: {
ThirdPartyEnum: ThirdPartyEnum.values,
ThirdPartyEnum2: EnumDescriptor(
values: ThirdPartyEnum2.values,
mapping: {
ThirdPartyEnum2.A: 'AAA',
ThirdPartyEnum2.B: 'BBB',
ThirdPartyEnum2.C: 'CCC'
}
),
ThirdPartyEnum3: EnumDescriptor(
values: ThirdPartyEnum3.values,
defaultValue: ThirdPartyEnum3.A,
mapping: {
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: )` 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.
``` dart
@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 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());
expect(target.businesses[1], TypeMatcher());
```## Classes enhanced with Mixins derived from abstract class
Similar configuration as above also works well for class mixins
```dart
@Json(discriminatorProperty: 'type')
@jsonSerializable
abstract class A {}@jsonSerializable
mixin B on A {}@jsonSerializable
class C extends A with B {}@jsonSerializable
class MixinContainer {
final Set 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(targetJson);// then
expect(targetJson, json);
expect(target, TypeMatcher());
expect(target.b, TypeMatcher());
```## 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` instance as a `template` parameter for
`SerializationOptions````dart
// 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` type then you can pass
typed instance of it as a `template` parameter for `DeserializationOptions`.Since typed `Map` instance cannot be created dynamically due to Dart
language nature, so you are providing ready made instance to use for deserialization output.```dart
// given
final json = '{"black":1,"blue":2}';// when
final target = JsonMapper.deserialize(
json, DeserializationOptions(template: {}));// then
expect(target, TypeMatcher>());
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][9], but that is not
always `true` for JSON models, they could follow
[one of those popular - Pascal, Kebab, Snake, SnakeAllCaps][10] 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)`.```dart
@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(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
```json
{
"root": {
"foo": {
"bar": {
"baz": {
"items": [
"a",
"b",
"c"
]
}
}
}
}
}
```And with code similar to this one
``` dart
@jsonSerializable
@Json(name: 'root/foo/bar')
class BarObject {
@JsonProperty(name: 'baz/items')
List items;BarObject({this.items});
}// when
final instance = JsonMapper.deserialize(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][rfc6901] 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 "../"
```json
[
{"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"}
]}
]
``````dart
@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 features;Product({this.name, this.id, this.features});
}@jsonSerializable
class ProductCategory {
num id;
String name;
List 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:```dart
@jsonSerializable
class Parent {
String? lastName;
List children = [];
}@jsonSerializable
class Child {
String? firstName;@JsonProperty(name: '..')
Parent parent;Child(this.parent);
}
```You are now able to deserialize the following structure:
```json
{
"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.```dart
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:
```json
{
"foo": "Bar"
}
``````dart
Outside outsideInstance = Outside();
final target = JsonMapper.deserialize(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.```dart
@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.``` dart
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:```json
{
"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.```dart
@jsonSerializable
class Pagination {
num? limit;
num? offset;
num? total;
}@jsonSerializable
class UsersPage {
@JsonProperty(flatten: true)
Pagination? pagination;List? 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.```dart
@jsonSerializable
class Pagination {
num? limit;
num? offset;
num? total;
}@jsonSerializable
@Json(caseStyle: CaseStyle.snake)
class UsersPage {
@JsonProperty(name: 'pagination', flatten: true)
Pagination? pagination;List? users;
}
```This will output
```json
{
"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][15] to accomplish that,
then this section probably will be useful for you```dart
// 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
```dart
// 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.```dart
/// Abstract class for custom converters implementations
abstract class ICustomConverter {
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
```dart
class CustomStringConverter implements ICustomConverter {
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
```dart
JsonMapper().useAdapter(JsonMapperAdapter(
converters: {
String: CustomStringConverter()
})
);
```OR use it individually on selected class fields, via `@JsonProperty` annotation
```dart
@JsonProperty(converter: CustomStringConverter())
String title;
```## Annotations
* `@JsonSerializable()` or `@jsonSerializable` for short, It's a **required** marker annotation for Class, Mixin, or Enum declarations.
Use it to mark all the Dart objects you'd like to be traveling to / from JSON
* Has **NO** params
* `@JsonConstructor()` or `@jsonConstructor` for short, It's an **optional** constructor only marker annotation.
Use it to mark specific Dart class constructor you'd like to be used during deserialization.
* *scheme* dynamic [Scheme](#schemes) marker to associate this meta information with particular mapping scheme
* `@Json(...)` It's an **optional** annotation for class declaration, describes a Dart object to JSON Object mapping.
Why it's not a `@JsonObject()`? just for you to type less characters :smile:
* *name* Defines [RFC 6901][rfc6901] JSON pointer, denotes the json Object root name/path to be used for mapping.
Example: `'foo', 'bar', 'foo/bar/baz'`
* *caseStyle* The most popular ways to combine words into a single string. Based on assumption: That all Dart class fields initially given as CaseStyle.camel
* *discriminatorProperty* Defines a class property to be used as a source of truth for discrimination logic in a hierarchy of inherited classes. Usually used on annotation of [abstract] class
* *discriminatorValue* Defines a custom override value for a discriminator. Usually used on annotations of subclasses, to distinguish it from each other. Default value: annotated _class name_
* *valueDecorators* Provides an inline way to specify a static function which will return a Map of value decorators, to support type casting activities for Map, and other generic Iterables instead of global adapter approach
* *ignoreNullMembers* If set to `true` Null class members will be excluded from serialization process
* *ignoreDefaultMembers* If set to `true` Class members having default value will be excluded from serialization process
* *processAnnotatedMembersOnly* If set to `true` Only annotated class members will be processed
* *allowCircularReferences* As of `int` type. Allows certain number of circular object references during serialization.
* *scheme* dynamic [Scheme](#schemes) marker to associate this meta information with particular mapping scheme
* `@JsonProperty(...)` It's an **optional** class member annotation, describes JSON Object property mapping.
* *name* Defines [RFC 6901][rfc6901] JSON pointer, denotes the name/path/aliases to be used for property mapping relative to the class *root nesting*
Example: `'foo', 'bar', 'foo/bar/baz', ['foo', 'bar', 'baz'], '../foo/bar'`
* *scheme* dynamic [Scheme](#schemes) marker to associate this meta information with particular mapping scheme
* *converter* Declares custom converter instance, to be used for annotated field serialization / deserialization
* *converterParams* A `Map` of parameters to be passed to the converter instance
* *flatten* Declares annotated field to be flattened and merged with the host object
* *notNull* A bool declares annotated field as NOT NULL for serialization / deserialization process
* *required* A bool declares annotated field as required for serialization / deserialization process i.e. needs to be present explicitly
* *inject* A bool Declares annotated field value to be directly injected from [DeserializationOptions.injectableValues] during deserialization process
* *ignore* A bool declares annotated field as ignored so it will be excluded from serialization / deserialization process
* *ignoreForSerialization* A bool declares annotated field as excluded from serialization process
* *ignoreForDeserialization* A bool declares annotated field as excluded from deserialization process
* *ignoreIfNull* A bool declares annotated field as ignored if it's value is null so it will be excluded from serialization process
* *ignoreIfDefault* A bool declares annotated field as ignored if it's value is equals to default so it will be excluded from serialization process
* *defaultValue* Defines field default value## Builder
This library introduces own builder used to pre-build Default adapter for your application code.
Technically, provided builder wraps the [reflectable][3] 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.
```yaml
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:
```yaml
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` and `Set` support for deserialization, then you could omit this option.And when you would like to have a deserialization support for other iterables like `HashSet, UnmodifiableListView`
you could add them to the list for this option.## Known limitations
* [Dart code obfuscation][obfuscation]. If you are using or planning to use `extra-gen-snapshot-options=--obfuscate` option with your Flutter project,
this library shouldn't be your primary choice then. At the moment there is no workaround for this to play nicely together.## 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:
* custom [converters](#custom-types)
* custom [value decorators](#iterable-types)
* custom typeInfo decorators
For example, you would like to refer to `Color` type from Flutter in your model class.* Make sure you have following dependencies in your `pubspec.yaml`:
```yaml
dependencies:
dart_json_mapper:
dart_json_mapper_flutter:
dev_dependencies:
build_runner:
```
* Usually, adapter library exposes `final` adapter definition instance, to be provided as a parameter to `JsonMapper().useAdapter(adapter)````dart
import 'dart:ui' show Color;
import 'package:dart_json_mapper/dart_json_mapper.dart' show JsonMapper, jsonSerializable;
import 'package:dart_json_mapper_flutter/dart_json_mapper_flutter.dart' show flutterAdapter;
import 'main.mapper.g.dart' show initializeJsonMapper;
@jsonSerializable
class ColorfulItem {
String name;
Color color;
ColorfulItem(this.name, this.color);
}
void main() {
initializeJsonMapper(adapters: [flutterAdapter]);
print(JsonMapper.serialize(
ColorfulItem('Item 1', Color(0x003f4f5f))
));
}
```
output:
```json
{
"name": "Item 1",
"color": "#003F4F5F"
}
```
### You can easily mix and combine several adapters using following one-liner:```dart
JsonMapper()
.useAdapter(fixnumAdapter)
.useAdapter(flutterAdapter)
.useAdapter(mobXAdapter)
.useAdapter(builtAdapter)
.info(); // print out a list of used adapters to console
```[1]: https://github.com/flutter/flutter/issues/1150
[2]: https://pub.dartlang.org/packages/intl
[3]: https://pub.dartlang.org/packages/reflectable
[4]: https://github.com/appvision-gmbh/json2typescript
[5]: https://github.com/serde-rs/serde
[6]: https://github.com/google/gson
[7]: https://github.com/mobxjs/mobx.dart
[8]: https://github.com/dart-lang/fixnum
[9]: https://en.wikipedia.org/wiki/Camel_case
[10]: https://medium.com/better-programming/string-case-styles-camel-pascal-snake-and-kebab-case-981407998841
[11]: https://github.com/flutter/flutter
[12]: https://www.baeldung.com/jackson-annotations
[13]: https://pub.dev/packages/build#implementing-your-own-builders
[14]: https://pub.dev/packages/super_enum
[15]: https://pub.dev/packages/freezed
[16]: https://pub.dev/packages/built_collection[obfuscation]: https://flutter.dev/docs/deployment/obfuscate
[rfc6901]: https://tools.ietf.org/html/rfc6901
[docs]: https://pub.dev/documentation/dart_json_mapper/latest/dart_json_mapper/dart_json_mapper-library.html
[ci-badge]: https://github.com/k-paxian/dart-json-mapper/workflows/Pipeline/badge.svg
[ci-badge-url]: https://github.com/k-paxian/dart-json-mapper/actions?query=workflow%3A%22Pipeline%22