dart_json_mapper 1.1.7 dart_json_mapper: ^1.1.7 copied to clipboard
This package allows programmers to annotate Dart classes in order to serialize / deserialize them from / to JSON.
dart-json-mapper #
This package allows programmers to annotate Dart classes in order to Serialize / Deserialize them to / from JSON.
Why? #
- Compatible with all Dart platforms, including Flutter and Web platforms
- No need to extend your classes from any mixins/base/abstract classes to keep code leaner
- Clean and simple setup, transparent and straightforward usage with no heavy maintenance
- No extra boilerplate involved, 100% generated only
- Custom converters support per each class field
Basic setup #
Please add the following dependencies to your pubspec.yaml
:
dependencies:
dart_json_mapper: any
dev_dependencies:
reflectable: ^2.0.8
build_runner: ^1.1.2
Library has NO dependency on dart:mirrors
, one of the reasons is described here.
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 accesible at runtime, at a reduced cost.
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.reflectable.dart file. And afterwards import that file into main.dart
lib/main.dart
import 'package:dart_json_mapper/annotations.dart';
import 'package:dart_json_mapper/json_mapper.dart';
import 'main.reflectable.dart'; // Import generated code.
@jsonSerializable // This annotation let instances of MyData traveling 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);
}
main() {
initializeReflectable(); // Imported from main.reflectable.dart
print(JsonMapper.serialize(MyData(456, true, "yes")));
// {
// "a": 456,
// "d": "yes"
// }
}
Go ahead and create a build.yaml
file in your project root directory. Then add the
following content:
targets:
$default:
builders:
reflectable:
generate_for:
- lib/main.dart
options:
formatted: true
Now run the code generation step with the root of your package as the current directory:
> pub run build_runner build
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
> pub run build_runner watch
Each time you modify your project code, all *.reflectable.dart files will be updated as well.
- Next step is to add "*.reflectable.dart" to your .gitignore
- And this is it, you are all set and ready to go. Happy coding!
Example with immutable class #
enum Color { Red, Blue, Green, Brown, Yellow, Black, White }
@jsonSerializable
class Car {
@JsonProperty(name: 'modelName')
String model;
@JsonProperty(enumValues: Color.values)
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": "Color.Green"
}
}
Iterable based types handling #
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>()
.
In order to do so, we'll use Value Decorator Function inspired by Decorator pattern.
final iterableCarDecorator = (value) => value.cast<Car>();
final String json = '[{"modelName": "Audi", "color": "Color.Green"}]';
JsonMapper.registerValueDecorator<List<Car>>(iterableCarDecorator);
JsonMapper.registerValueDecorator<Set<Car>>(iterableCarDecorator);
List<Car> myCarsList = JsonMapper.deserialize(json);
Set<Car> myCarsSet = JsonMapper.deserialize(json);
Basic iterable based generics using Dart built-in types like List<num>, List<Sring>, List<bool>, List<DateTime>, Set<num>, Set<Sring>, Set<bool>, Set<DateTime>, etc.
supported out of the box.
For custom iterable types like List<Car> / Set<Car>
you have to register value decorator function
as showed in a code snippet above before using deserialization. This function will have explicit
cast to concrete iterable type.
Enum based types handling #
Enum construction in Dart has a specific meaning, and has to be treated accordingly.
Enum declarations should not be annotated with @jsonSerializable
, since they are not a classes
technically, but a special built in types.
@JsonProperty(enumValues: Color.values)
Color color;
Each enum based class field has to be annotated as showed in a snippet above.
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.
Inherited classes derived from abstract / base class #
Please use complementary @Json(includeTypeName: true)
annotation for subclasses
derived from abstract or base class. This way dart-json-mapper
will dump the concrete object type to the JSON output during serialization process.
This ensures, that dart-json-mapper will be able to reconstruct the object with
the proper type during deserialization process.
@jsonSerializable
abstract class Business {
String name;
}
@jsonSerializable
@Json(includeTypeName: true)
class Hotel extends Business {
int stars;
Hotel(this.stars);
}
@jsonSerializable
@Json(includeTypeName: true)
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
JsonMapper.registerValueDecorator<List<Business>>((value) => value.cast<Business>());
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>());
Using static JsonMapper.typeNameProperty
you can specify suitable name
for the json property, which will contain the object type:
JsonMapper.typeNameProperty = "objectType";
Custom based types handling #
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, [JsonProperty jsonProperty]);
T fromJSON(dynamic jsonValue, [JsonProperty jsonProperty]);
}
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, [JsonProperty jsonProperty]) {
return jsonValue;
}
@override
dynamic toJSON(String object, [JsonProperty jsonProperty]) {
return '_${object}_';
}
}
And register it afterwards, if you want to have it applied for all occurrences of specified type
JsonMapper.registerConverter<String>(CustomStringConverter());
OR use it individually on selected class fields, via @JsonProperty
annotation
@JsonProperty(converter: CustomStringConverter())
String title;