| Index: pkg/serialization/lib/src/format.dart
|
| diff --git a/pkg/serialization/lib/src/format.dart b/pkg/serialization/lib/src/format.dart
|
| index c1d122e064bfc999e1d268fa0b3a91cd7172750b..ab602c92a2b80c29997324b736f7a592c07b1f08 100644
|
| --- a/pkg/serialization/lib/src/format.dart
|
| +++ b/pkg/serialization/lib/src/format.dart
|
| @@ -52,23 +52,22 @@ class SimpleMapFormat extends Format {
|
| * This effectively defines a custom JSON serialization format, although
|
| * the details of the format vary depending which rules were used.
|
| */
|
| - String generateOutput(Writer w) {
|
| + Map<String, dynamic> generateOutput(Writer w) {
|
| var result = {
|
| "rules" : w.serializedRules(),
|
| "data" : w.states,
|
| "roots" : w._rootReferences()
|
| };
|
| - return json.stringify(result);
|
| + return result;
|
| }
|
|
|
| /**
|
| - * Read a [json] encoded string representing serialized data in this format
|
| + * Read a [json] compatible representation of serialized data in this format
|
| * and return the nested Map representation described in [generateOutput]. If
|
| * the data also includes rule definitions, then these will replace the rules
|
| * in the [Serialization] for [reader].
|
| */
|
| - Map<String, dynamic> read(String input, Reader reader) {
|
| - var topLevel = json.parse(input);
|
| + Map<String, dynamic> read(topLevel, Reader reader) {
|
| var ruleString = topLevel["rules"];
|
| reader.readRules(ruleString);
|
| return topLevel;
|
| @@ -76,12 +75,13 @@ class SimpleMapFormat extends Format {
|
| }
|
|
|
| /**
|
| - * A format for "normal" JSON representation of objects. It stores
|
| + * A format for "normal" [json] representation of objects. It stores
|
| * the fields of the objects as nested maps, and doesn't allow cycles. This can
|
| - * be useful in talking to existing APIs that expect JSON format data. However,
|
| - * note that since the classes of objects aren't stored, this isn't enough
|
| - * information to read back the objects. This format also doesn't support the
|
| - * [selfDescriptive] option on the [Serialization], as storing the rules.
|
| + * be useful in talking to existing APIs that expect [json] format data. The
|
| + * output will be either a simple object (string, num, bool), a List, or a Map,
|
| + * with nesting of those.
|
| + * Note that since the classes of objects aren't normally stored, this isn't
|
| + * enough information to read back the objects. However, if the
|
| * If the [storeRoundTripData] field of the format is set to true, then this
|
| * will store the rule number along with the data, allowing reconstruction.
|
| */
|
| @@ -98,17 +98,27 @@ class SimpleJsonFormat extends Format {
|
| /**
|
| * If we store the rule numbers, what key should we use to store them.
|
| */
|
| - String ruleIdentifier = "__rule";
|
| + static final String RULE = "_rule";
|
| + static final String RULES = "_rules";
|
| + static final String DATA = "_data";
|
| + static final String ROOTS = "_root";
|
|
|
| SimpleJsonFormat({this.storeRoundTripInfo : false});
|
|
|
| /**
|
| - * Generate output for this format from [w] and return it as a String which
|
| - * is the [json] representation of a nested Map structure.
|
| + * Generate output for this format from [w] and return it as
|
| + * the [json] representation of a nested Map structure.
|
| */
|
| - String generateOutput(Writer w) {
|
| + generateOutput(Writer w) {
|
| jsonify(w);
|
| - return json.stringify(w.stateForReference(w._rootReferences().first));
|
| + var root = w._rootReferences().first;
|
| + if (root is Reference) root = w.stateForReference(root);
|
| + if (w.selfDescribing && storeRoundTripInfo) {
|
| + root = new Map()
|
| + ..[RULES] = w.serializedRules()
|
| + ..[DATA] = root;
|
| + }
|
| + return root;
|
| }
|
|
|
| /**
|
| @@ -134,7 +144,7 @@ class SimpleJsonFormat extends Format {
|
| if (storeRoundTripInfo) ruleData[i].add(rule.number);
|
| } else if (each is Map) {
|
| jsonifyEntry(each, w);
|
| - if (storeRoundTripInfo) each[ruleIdentifier] = rule.number;
|
| + if (storeRoundTripInfo) each[RULE] = rule.number;
|
| }
|
| }
|
| }
|
| @@ -150,19 +160,29 @@ class SimpleJsonFormat extends Format {
|
| }
|
|
|
| /**
|
| - * Read a [json] encoded string representing serialized data in this format
|
| - * and return the Map representation that the reader expects, with top-level
|
| + * Read serialized data saved in this format, which should look like
|
| + * either a simple type, a List or a Map and return the Map
|
| + * representation that the reader expects, with top-level
|
| * entries for "rules", "data", and "roots". Nested lists/maps will be
|
| * converted into Reference objects. Note that if the data was not written
|
| * with [storeRoundTripInfo] true this will fail.
|
| */
|
| - Map<String, dynamic> read(String input, Reader r) {
|
| - var data = json.parse(input);
|
| - var result = {};
|
| - result["rules"] = null;
|
| - var ruleData =
|
| - new List(r.serialization.rules.length).map((x) => []).toList();
|
| - var top = recursivelyFixUp(data, r, ruleData);
|
| + Map<String, dynamic> read(data, Reader reader) {
|
| + var result = new Map();
|
| + // Check the case of having been written without additional data and
|
| + // read as if it had been written with storeRoundTripData set.
|
| + if (reader.selfDescribing && !(data.containsKey(DATA))) {
|
| + throw new SerializationException("Missing $DATA entry, "
|
| + "may mean this was written and read with different values "
|
| + "of selfDescribing.");
|
| + }
|
| + // If we are self-describing, we should have separate rule and data
|
| + // sections. If not, we assume that we have just the data at the top level.
|
| + var rules = reader.selfDescribing ? data[RULES] : null;
|
| + var actualData = reader.selfDescribing ? data[DATA] : data;
|
| + reader.readRules(rules);
|
| + var ruleData = new List(reader.rules.length).map((x) => []).toList();
|
| + var top = recursivelyFixUp(actualData, reader, ruleData);
|
| result["data"] = ruleData;
|
| result["roots"] = [top];
|
| return result;
|
| @@ -171,20 +191,28 @@ class SimpleJsonFormat extends Format {
|
| /**
|
| * Convert nested references in [data] into [Reference] objects.
|
| */
|
| - recursivelyFixUp(data, Reader r, List result) {
|
| + recursivelyFixUp(input, Reader r, List result) {
|
| + var data = input;
|
| if (isPrimitive(data)) {
|
| result[r._primitiveRule().number].add(data);
|
| return data;
|
| }
|
| var ruleNumber;
|
| + // If we've added the rule number on as the last item in a list we have
|
| + // to get rid of it or it will be interpreted as extra data. For a map
|
| + // the library will be ok, but we need to get rid of the extra key before
|
| + // the data is shown to the user, so we destructively modify.
|
| if (data is List) {
|
| - ruleNumber = data.removeLast();
|
| + ruleNumber = data.last;
|
| + data = data.take(data.length -1);
|
| } else if (data is Map) {
|
| - ruleNumber = data.remove(ruleIdentifier);
|
| + ruleNumber = data.remove(RULE);
|
| } else {
|
| throw new SerializationException("Invalid data format");
|
| }
|
| - var newData = mapValues(data, (x) => recursivelyFixUp(x, r, result));
|
| + // Do not use mappedBy or other lazy operations for this. They do not play
|
| + // well with a function that destructively modifies its arguments.
|
| + var newData = mapValues(data, (each) => recursivelyFixUp(each, r, result));
|
| result[ruleNumber].add(newData);
|
| return new Reference(r, ruleNumber, result[ruleNumber].length - 1);
|
| }
|
|
|