| Index: pkg/serialization/lib/serialization.dart
|
| diff --git a/pkg/serialization/lib/serialization.dart b/pkg/serialization/lib/serialization.dart
|
| index 2b1377280a35a2e7fd74a23f7dc3811e17cdd0e7..d3e437b34fe6f88f19d32b0516d28c423765d055 100644
|
| --- a/pkg/serialization/lib/serialization.dart
|
| +++ b/pkg/serialization/lib/serialization.dart
|
| @@ -19,10 +19,10 @@
|
| * String 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 we can't write Address
|
| - * as a literal. Then we ask the Serialization to write the address and we get
|
| - * back a String which is a [JSON] representation of the state of it and related
|
| - * objects.
|
| + * 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 String which is a [JSON] representation of the state of
|
| + * it and related objects.
|
| *
|
| * The version above used reflection to automatically identify the public
|
| * fields of the address object. We can also specify those fields explicitly.
|
| @@ -44,9 +44,45 @@
|
| * 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 cumbersome, but it does work in
|
| - * dart2js, where mirrors are not yet implemented.
|
| + * 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 [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 [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
|
| + * [create] and [setState] methods interpret the same way as [getState] does.
|
| + *
|
| + * The [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 [create] and do more of the work in
|
| + * [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};
|
| @@ -54,30 +90,20 @@
|
| * fillInAddress(Map m, Address a) => a.city = m["city"];
|
| * var serialization = new Serialization()
|
| * ..addRule(
|
| - * new ClosureToMapRule(anAddress.runtimeType,
|
| + * new ClosureRule(anAddress.runtimeType,
|
| * addressToMap, createAddress, fillInAddress);
|
| *
|
| - * Note that there are three different functions provided. The addressToMap
|
| - * function takes the fields we want serialized from the Address and puts them
|
| - * into a map. The createAddress function creates a new address using a map like
|
| - * the one returned by the first function. And the fillInAddress function fills
|
| - * in any remaining state in the created object.
|
| - *
|
| - * At the moment, however, using this rule increases the probability of problems
|
| - * with cycles. The problem is that before passing values to the user-supplied
|
| - * functions it has to inflate any references to be the real objects. Since it
|
| - * doesn't know which ones the creation function uses it has to inflate all of
|
| - * them. For example, consider a Node class with parent, leftChild, and
|
| - * rightChild, and the parent field was final and set in the constructor. When
|
| - * we inflate all of the values we will end up with a cycle and can't
|
| - * de-serialize. If we know which fields are used by the constructor we can
|
| - * inflate only those, which is what BasicRule does. We expect to make a richer
|
| - * API for rules not using reflection, but there's a tension between providing
|
| - * the serialization process with enough information and making it more work
|
| - * to specify.
|
| + * 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
|
| - * the serialized object. For example, we may just want to pass null, or a
|
| + * 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, it will be
|
| * treated as a constant and passed unaltered to the constructor.
|
| @@ -93,34 +119,34 @@
|
| *
|
| * Writing
|
| * =======
|
| - * To write objects, we use the write() methods. There are two variations.
|
| + * To write objects, we use the write() method.
|
| + *
|
| + * var output = serialization.write(someObject);
|
| *
|
| - * String output = serialization.write(someObject);
|
| - * List output = serialization.writeFlat(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 encoded as a [json] string.
|
| *
|
| - * The first 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 encoded as a [JSON] string.
|
| + * We can write objects in different formats by passing a [Format] object to
|
| + * the [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
|
| *
|
| - * The second representation holds all the objects as a List of simple types.
|
| - * For practical use you may want to convert that to a [JSON] or other encoded
|
| - * representation as well.
|
| + * String output = serialization.write(address, new SimpleMapFormat());
|
| + * List output = serialization.write(address, new SimpleFlatFormat());
|
| + * Map output = serialization.write(address, new SimpleJsonFormat());
|
| + * Or, using a [Writer] explicitly
|
| + * var writer = serialization.newWriter(new SimpleFlatFormat());
|
| + * var output = writer.write(address);
|
| *
|
| - * Both representations are primarily intended as proofs of concept for
|
| - * different types of representation, and we expect to generalize that to a
|
| - * pluggable mechanism for different representations.
|
| + * These representations are not yet considered stable.
|
| *
|
| * Reading
|
| * =======
|
| - * To read objects, the corresponding methods are [read] and [readFlat].
|
| + * To read objects, the corresponding [read] method can be used.
|
| *
|
| - * List input = serialization.read(aString);
|
| - * List input = serialization.readFlat(aList);
|
| - *
|
| - * There is also a convenience method for the case of reading a single object.
|
| - *
|
| - * Object result = serialization.readOne(aString);
|
| - * Object result = serialization.readOneFlat(aString);
|
| + * Address input = serialization.read(aString);
|
| *
|
| * 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
|
| @@ -129,21 +155,27 @@
|
| * same. The simplest way to achieve this is by having the serialization
|
| * variable [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 does not yet work for [ClosureToMapRule]. The
|
| - * [selfDescribing] variable is true by default.
|
| + * end. Note that this may not work for all rules or all formats. The
|
| + * [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 [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 [externalObjects] variable of the Serialization,
|
| - * or as an additional parameter to the reading methods.
|
| + * be provided either in the [namedObjects] variable of the Serialization,
|
| + * or as an additional parameter to the reading and writing methods on the
|
| + * [Reader] or [Writer] respectively.
|
| *
|
| * new Serialization()
|
| * ..addRuleFor(new Person(), constructorFields: ["name"])
|
| - * ..externalObjects['Person'] = reflect(new Person()).type;
|
| + * ..namedObjects['Person'] = reflect(new Person()).type;
|
| */
|
| library serialization;
|
|
|
| @@ -155,6 +187,7 @@ import 'dart:json' as json;
|
| part 'src/reader_writer.dart';
|
| part 'src/serialization_rule.dart';
|
| part 'src/basic_rule.dart';
|
| +part 'src/format.dart';
|
|
|
| /**
|
| * This class defines a particular serialization scheme, in terms of
|
| @@ -291,13 +324,12 @@ class Serialization {
|
| }
|
|
|
| /**
|
| - * This is the basic method to write out an object graph rooted at
|
| - * [object] and return the result. Right now this is hard-coded to return
|
| - * a String from a custom [JSON] format, but that is likely to change to be
|
| - * more pluggable in the near future.
|
| + * This writes out an object graph rooted at [object] and returns the result.
|
| + * The [format] parameter determines the form of the result. The default
|
| + * format returns a String in [json] format.
|
| */
|
| - String write(Object object) {
|
| - return newWriter().write(object);
|
| + write(Object object, [Format format]) {
|
| + return newWriter(format).write(object);
|
| }
|
|
|
| /**
|
| @@ -305,15 +337,8 @@ class Serialization {
|
| * want to do something more complex with the writer than just returning
|
| * the final result.
|
| */
|
| - Writer newWriter() => new Writer(this);
|
| -
|
| - /**
|
| - * Write out the tree in a custom flat format, returning a list containing
|
| - * only "simple" types: num, String, and bool.
|
| - */
|
| - List writeFlat(Object object) {
|
| - return newWriter().writeFlat(object);
|
| - }
|
| + Writer newWriter([Format format]) =>
|
| + new Writer(this, format);
|
|
|
| /**
|
| * Read the serialized data from [input] and return the root object
|
| @@ -333,13 +358,13 @@ class Serialization {
|
| * you want to do something more complex with the reader than just returning
|
| * the final result.
|
| */
|
| - Reader newReader() => new Reader(this);
|
| + Reader newReader([Format format]) => new Reader(this, format);
|
|
|
| /**
|
| * Return the list of SerializationRule that apply to [object]. For
|
| * internal use, but public because it's used in testing.
|
| */
|
| - List<SerializationRule> rulesFor(object, Writer w) {
|
| + Iterable<SerializationRule> rulesFor(object, Writer w) {
|
| // This has a couple of edge cases.
|
| // 1) The owning object may have indicated we should use a different
|
| // rule than the default.
|
| @@ -363,17 +388,17 @@ class Serialization {
|
| target = object;
|
| candidateRules = _rules;
|
| }
|
| - List applicable =
|
| - candidateRules.where((each) => each.appliesTo(target, w)).toList();
|
| + Iterable applicable = candidateRules.where(
|
| + (each) => each.appliesTo(target, w));
|
|
|
| if (applicable.isEmpty) {
|
| return [addRuleFor(target)];
|
| }
|
|
|
| if (applicable.length == 1) return applicable;
|
| - var first = applicable[0];
|
| + var first = applicable.first;
|
| var finalRules = applicable.where(
|
| - (x) => !x.mustBePrimary || (x == first)).toList();
|
| + (x) => !x.mustBePrimary || (x == first));
|
|
|
| if (finalRules.isEmpty) throw new SerializationException(
|
| 'No valid rule found for object $object');
|
| @@ -386,7 +411,7 @@ class Serialization {
|
| * If there are new rule classes created, they will need to be described
|
| * here.
|
| */
|
| - Serialization _ruleSerialization() {
|
| + Serialization ruleSerialization() {
|
| // TODO(alanknight): There's an extensibility issue here with new rules.
|
| // TODO(alanknight): How to handle rules with closures? They have to
|
| // exist on the other side, but we might be able to hook them up by name,
|
|
|