Dart API Referenceserialization

serialization library

This provides a general-purpose serialization facility for Dart objects. A Serialization is defined in terms of SerializationRules and supports reading and writing to different formats.

Installing

Use pub to install this package. Add the following to your pubspec.yaml file.

dependencies:
  serialization: any

Then run pub install.

For more information, see the serialization package on pub.dartlang.org.

Setup

A simple example of usage is

 var address = new Address();
 address.street = 'N 34th';
 address.city = 'Seattle';
 var serialization = new Serialization()
     ..addRuleFor(address);
 Map output = serialization.write(address);

This creates a new serialization and adds a rule for address objects. Right now it has to be passed an address instance because of limitations using Address as a literal. Then we ask the Serialization to write the address and we get back a Map which is a jsonable representation of the state of the address and related objects. Note that while the output in this case is a Map, the type will vary depending on which output format we've told the Serialization to use.

The version above used reflection to automatically identify the public fields of the address object. We can also specify those fields explicitly.

 var serialization = new Serialization()
   ..addRuleFor(address,
       constructor: "create",
       constructorFields: ["number", "street"],
       fields: ["city"]);

This rule still uses reflection to access the fields, but does not try to identify which fields to use, but instead uses only the "number" and "street" fields that we specified. We may also want to tell it to identify the fields, but to specifically omit certain fields that we don't want serialized.

 var serialization = new Serialization()
   ..addRuleFor(address,
       constructor: "",
       excludeFields: ["other", "stuff"]);

Writing Rules

We can also use a completely non-reflective rule to serialize and de-serialize objects. This can be more work, but it does work in dart2js, where mirrors are not yet implemented. We can specify this in two ways. First, we can write our own SerializationRule class that has methods for our Address class.

 class AddressRule extends CustomRule {
   bool appliesTo(instance, Writer w) => instance.runtimeType == Address;
   getState(instance) => [instance.street, instance.city];
   create(state) => new Address();
   setState(Address a, List state) {
     a.street = state[0];
     a.city = state[1];
   }
 }

The class needs four different methods. The CustomRule.appliesTo method tells us if the rule should be used to write an object. In this example we use a test based on runtimeType. We could also use an "is Address" test, but if Address has subclasses that would find those as well, and we want a separate rule for each. The CustomRule.getState method should return all the state of the object that we want to recreate, and should be either a Map or a List. If you want to write to human-readable formats where it's useful to be able to look at the data as a map from field names to values, then it's better to return it as a map. Otherwise it's more efficient to return it as a list. You just need to be sure that the CustomRule.create and CustomRule.setState methods interpret the data the same way as CustomRule.getState does.

The CustomRule.create method will create the new object and return it. While it's possible to create the object and set all its state in this one method, that increases the likelihood of problems with cycles. So it's better to use the minimum necessary information in CustomRule.create and do more of the work in CustomRule.setState.

The other way to do this is not creating a subclass, but by using a ClosureRule and giving it functions for how to create the address.

 addressToMap(a) => {"number" : a.number, "street" : a.street,
     "city" : a.city};
 createAddress(Map m) => new Address.create(m["number"], m["street"]);
 fillInAddress(Address a, Map m) => a.city = m["city"];
 var serialization = new Serialization()
   ..addRule(
       new ClosureRule(anAddress.runtimeType,
           addressToMap, createAddress, fillInAddress);

In this case we have created standalone functions rather than methods in a subclass and we pass them to the constructor of ClosureRule. In this case we've also had them use maps rather than lists for the state, but either would work as long as the rule is consistent with the representation it uses. We pass it the runtimeType of the object, and functions equivalent to the methods on CustomRule

Constant Values

There are cases where the constructor needs values that we can't easily get from the serialized object. For example, we may just want to pass null, or a constant value. To support this, we can specify as constructor fields values that aren't field names. If any value isn't a String, or is a string that doesn't correspond to a field name, it will be treated as a constant and passed unaltered to the constructor.

In some cases a non-constructor field should not be set using field access or a setter, but should be done by calling a method. For example, it may not be possible to set a List field "foo", and you need to call an addFoo() method for each entry in the list. In these cases, if you are using a BasicRule for the object you can call the setFieldWith() method.

  s..addRuleFor(fooHolderInstance).setFieldWith("foo",
      (parent, value) => for (var each in value) parent.addFoo(value));

Writing

To write objects, we use the write() method.

  var output = serialization.write(someObject);

By default this uses a representation in which objects are represented as maps keyed by field name, but in which references between objects have been converted into Reference objects. This is then typically encoded as a json string, but can also be used in other ways, e.g. sent to another isolate.

We can write objects in different formats by passing a Format object to the Serialization.write method or by getting a Writer object. The available formats include the default, a simple "flat" format that doesn't include field names, and a simple JSON format that produces output more suitable for talking to services that expect JSON in a predefined format. Examples of these are

 Map output = serialization.write(address, new SimpleMapFormat());
 List output = serialization.write(address, new SimpleFlatFormat());
 var output = serialization.write(address, new SimpleJsonFormat());

Or, using a Writer explicitly

 var writer = serialization.newWriter(new SimpleFlatFormat());
 List output = writer.write(address);

These representations are not yet considered stable.

Reading

To read objects, the corresponding Serialization.read method can be used.

  Address input = serialization.read(input);

When reading, the serialization instance doing the reading must be configured with compatible rules to the one doing the writing. It's possible for the rules to be different, but they need to be able to read the same representation. For most practical purposes right now they should be the same. The simplest way to achieve this is by having the serialization variable Serialization.selfDescribing be true. In that case the rules themselves are also stored along with the serialized data, and can be read back on the receiving end. Note that this may not work for all rules or all formats. The Serialization.selfDescribing variable is true by default, but the SimpleJsonFormat does not support it, since the point is to provide a representation in a form other services might expect. Using CustomRule or ClosureRule also does not yet work with the Serialization.selfDescribing variable.

Named Objects

When reading, some object references should not be serialized, but should be connected up to other instances on the receiving side. A notable example of this is when serialization rules have been stored. Instances of BasicRule take a ClassMirror in their constructor, and we cannot serialize those. So when we read the rules, we must provide a Map<String, Object> which maps from the simple name of classes we are interested in to a ClassMirror. This can be provided either in the Serialization.namedObjects, or as an additional parameter to the reading and writing methods on the Reader or Writer respectively.

new Serialization()
  ..addRuleFor(new Person(), constructorFields: ["name"])
  ..namedObjects['Person'] = reflect(new Person()).type;

Abstract Classes

Classes

Typedefs

Exceptions