| OLD | NEW |
| 1 part of serialization; | 1 part of serialization; |
| 2 | 2 |
| 3 /** | 3 /** |
| 4 * An abstract class for serialization formats. Subclasses define how data | 4 * An abstract class for serialization formats. Subclasses define how data |
| 5 * is read or written to a particular output mechanism. | 5 * is read or written to a particular output mechanism. |
| 6 */ | 6 */ |
| 7 abstract class Format { | 7 abstract class Format { |
| 8 /** | 8 /** |
| 9 * Return true if this format stores primitives in their own area and uses | 9 * Return true if this format stores primitives in their own area and uses |
| 10 * references to them (e.g. [SimpleFlatFormat]) and false if primitives | 10 * references to them (e.g. [SimpleFlatFormat]) and false if primitives |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 45 * is the [json] representation of a nested Map structure. The top level has | 45 * is the [json] representation of a nested Map structure. The top level has |
| 46 * 3 fields, "rules" which may hold a definition of the rules used, | 46 * 3 fields, "rules" which may hold a definition of the rules used, |
| 47 * "data" which holds the serialized data, and "roots", which holds | 47 * "data" which holds the serialized data, and "roots", which holds |
| 48 * [Reference] objects indicating the root objects. Note that roots are | 48 * [Reference] objects indicating the root objects. Note that roots are |
| 49 * necessary because the data is organized in the same way as the object | 49 * necessary because the data is organized in the same way as the object |
| 50 * structure, it's a list of lists holding self-contained maps which only | 50 * structure, it's a list of lists holding self-contained maps which only |
| 51 * refer to other parts via [Reference] objects. | 51 * refer to other parts via [Reference] objects. |
| 52 * This effectively defines a custom JSON serialization format, although | 52 * This effectively defines a custom JSON serialization format, although |
| 53 * the details of the format vary depending which rules were used. | 53 * the details of the format vary depending which rules were used. |
| 54 */ | 54 */ |
| 55 String generateOutput(Writer w) { | 55 Map<String, dynamic> generateOutput(Writer w) { |
| 56 var result = { | 56 var result = { |
| 57 "rules" : w.serializedRules(), | 57 "rules" : w.serializedRules(), |
| 58 "data" : w.states, | 58 "data" : w.states, |
| 59 "roots" : w._rootReferences() | 59 "roots" : w._rootReferences() |
| 60 }; | 60 }; |
| 61 return json.stringify(result); | 61 return result; |
| 62 } | 62 } |
| 63 | 63 |
| 64 /** | 64 /** |
| 65 * Read a [json] encoded string representing serialized data in this format | 65 * Read a [json] compatible representation of serialized data in this format |
| 66 * and return the nested Map representation described in [generateOutput]. If | 66 * and return the nested Map representation described in [generateOutput]. If |
| 67 * the data also includes rule definitions, then these will replace the rules | 67 * the data also includes rule definitions, then these will replace the rules |
| 68 * in the [Serialization] for [reader]. | 68 * in the [Serialization] for [reader]. |
| 69 */ | 69 */ |
| 70 Map<String, dynamic> read(String input, Reader reader) { | 70 Map<String, dynamic> read(topLevel, Reader reader) { |
| 71 var topLevel = json.parse(input); | |
| 72 var ruleString = topLevel["rules"]; | 71 var ruleString = topLevel["rules"]; |
| 73 reader.readRules(ruleString); | 72 reader.readRules(ruleString); |
| 74 return topLevel; | 73 return topLevel; |
| 75 } | 74 } |
| 76 } | 75 } |
| 77 | 76 |
| 78 /** | 77 /** |
| 79 * A format for "normal" JSON representation of objects. It stores | 78 * A format for "normal" [json] representation of objects. It stores |
| 80 * the fields of the objects as nested maps, and doesn't allow cycles. This can | 79 * the fields of the objects as nested maps, and doesn't allow cycles. This can |
| 81 * be useful in talking to existing APIs that expect JSON format data. However, | 80 * be useful in talking to existing APIs that expect [json] format data. The |
| 82 * note that since the classes of objects aren't stored, this isn't enough | 81 * output will be either a simple object (string, num, bool), a List, or a Map, |
| 83 * information to read back the objects. This format also doesn't support the | 82 * with nesting of those. |
| 84 * [selfDescriptive] option on the [Serialization], as storing the rules. | 83 * Note that since the classes of objects aren't normally stored, this isn't |
| 84 * enough information to read back the objects. However, if the |
| 85 * If the [storeRoundTripData] field of the format is set to true, then this | 85 * If the [storeRoundTripData] field of the format is set to true, then this |
| 86 * will store the rule number along with the data, allowing reconstruction. | 86 * will store the rule number along with the data, allowing reconstruction. |
| 87 */ | 87 */ |
| 88 class SimpleJsonFormat extends Format { | 88 class SimpleJsonFormat extends Format { |
| 89 | 89 |
| 90 /** | 90 /** |
| 91 * Indicate if we should store rule numbers with map/list data so that we | 91 * Indicate if we should store rule numbers with map/list data so that we |
| 92 * will know how to reconstruct it with a read operation. If we don't, this | 92 * will know how to reconstruct it with a read operation. If we don't, this |
| 93 * will be more compliant with things that expect known format JSON as input, | 93 * will be more compliant with things that expect known format JSON as input, |
| 94 * but we won't be able to read back the objects. | 94 * but we won't be able to read back the objects. |
| 95 */ | 95 */ |
| 96 final bool storeRoundTripInfo; | 96 final bool storeRoundTripInfo; |
| 97 | 97 |
| 98 /** | 98 /** |
| 99 * If we store the rule numbers, what key should we use to store them. | 99 * If we store the rule numbers, what key should we use to store them. |
| 100 */ | 100 */ |
| 101 String ruleIdentifier = "__rule"; | 101 static final String RULE = "_rule"; |
| 102 static final String RULES = "_rules"; |
| 103 static final String DATA = "_data"; |
| 104 static final String ROOTS = "_root"; |
| 102 | 105 |
| 103 SimpleJsonFormat({this.storeRoundTripInfo : false}); | 106 SimpleJsonFormat({this.storeRoundTripInfo : false}); |
| 104 | 107 |
| 105 /** | 108 /** |
| 106 * Generate output for this format from [w] and return it as a String which | 109 * Generate output for this format from [w] and return it as |
| 107 * is the [json] representation of a nested Map structure. | 110 * the [json] representation of a nested Map structure. |
| 108 */ | 111 */ |
| 109 String generateOutput(Writer w) { | 112 generateOutput(Writer w) { |
| 110 jsonify(w); | 113 jsonify(w); |
| 111 return json.stringify(w.stateForReference(w._rootReferences().first)); | 114 var root = w._rootReferences().first; |
| 115 if (root is Reference) root = w.stateForReference(root); |
| 116 if (w.selfDescribing && storeRoundTripInfo) { |
| 117 root = new Map() |
| 118 ..[RULES] = w.serializedRules() |
| 119 ..[DATA] = root; |
| 120 } |
| 121 return root; |
| 112 } | 122 } |
| 113 | 123 |
| 114 /** | 124 /** |
| 115 * Convert the data generated by the rules to have nested maps instead | 125 * Convert the data generated by the rules to have nested maps instead |
| 116 * of Reference objects and to add rule numbers if [storeRoundTripInfo] | 126 * of Reference objects and to add rule numbers if [storeRoundTripInfo] |
| 117 * is true. | 127 * is true. |
| 118 */ | 128 */ |
| 119 jsonify(Writer w) { | 129 jsonify(Writer w) { |
| 120 for (var eachRule in w.rules) { | 130 for (var eachRule in w.rules) { |
| 121 var ruleData = w.states[eachRule.number]; | 131 var ruleData = w.states[eachRule.number]; |
| 122 jsonifyForRule(ruleData, w, eachRule); | 132 jsonifyForRule(ruleData, w, eachRule); |
| 123 } | 133 } |
| 124 } | 134 } |
| 125 | 135 |
| 126 /** | 136 /** |
| 127 * For a particular [rule] modify the [ruleData] to conform to this format. | 137 * For a particular [rule] modify the [ruleData] to conform to this format. |
| 128 */ | 138 */ |
| 129 jsonifyForRule(List ruleData, Writer w, SerializationRule rule) { | 139 jsonifyForRule(List ruleData, Writer w, SerializationRule rule) { |
| 130 for (var i = 0; i < ruleData.length; i++) { | 140 for (var i = 0; i < ruleData.length; i++) { |
| 131 var each = ruleData[i]; | 141 var each = ruleData[i]; |
| 132 if (each is List) { | 142 if (each is List) { |
| 133 jsonifyEntry(each, w); | 143 jsonifyEntry(each, w); |
| 134 if (storeRoundTripInfo) ruleData[i].add(rule.number); | 144 if (storeRoundTripInfo) ruleData[i].add(rule.number); |
| 135 } else if (each is Map) { | 145 } else if (each is Map) { |
| 136 jsonifyEntry(each, w); | 146 jsonifyEntry(each, w); |
| 137 if (storeRoundTripInfo) each[ruleIdentifier] = rule.number; | 147 if (storeRoundTripInfo) each[RULE] = rule.number; |
| 138 } | 148 } |
| 139 } | 149 } |
| 140 } | 150 } |
| 141 | 151 |
| 142 /** | 152 /** |
| 143 * For one particular entry, which is either a Map or a List, update it | 153 * For one particular entry, which is either a Map or a List, update it |
| 144 * to turn References into a nested List/Map. | 154 * to turn References into a nested List/Map. |
| 145 */ | 155 */ |
| 146 jsonifyEntry(map, Writer w) { | 156 jsonifyEntry(map, Writer w) { |
| 147 keysAndValues(map).forEach((key, value) { | 157 keysAndValues(map).forEach((key, value) { |
| 148 if (value is Reference) map[key] = w.stateForReference(value); | 158 if (value is Reference) map[key] = w.stateForReference(value); |
| 149 }); | 159 }); |
| 150 } | 160 } |
| 151 | 161 |
| 152 /** | 162 /** |
| 153 * Read a [json] encoded string representing serialized data in this format | 163 * Read serialized data saved in this format, which should look like |
| 154 * and return the Map representation that the reader expects, with top-level | 164 * either a simple type, a List or a Map and return the Map |
| 165 * representation that the reader expects, with top-level |
| 155 * entries for "rules", "data", and "roots". Nested lists/maps will be | 166 * entries for "rules", "data", and "roots". Nested lists/maps will be |
| 156 * converted into Reference objects. Note that if the data was not written | 167 * converted into Reference objects. Note that if the data was not written |
| 157 * with [storeRoundTripInfo] true this will fail. | 168 * with [storeRoundTripInfo] true this will fail. |
| 158 */ | 169 */ |
| 159 Map<String, dynamic> read(String input, Reader r) { | 170 Map<String, dynamic> read(data, Reader reader) { |
| 160 var data = json.parse(input); | 171 var result = new Map(); |
| 161 var result = {}; | 172 // Check the case of having been written without additional data and |
| 162 result["rules"] = null; | 173 // read as if it had been written with storeRoundTripData set. |
| 163 var ruleData = | 174 if (reader.selfDescribing && !(data.containsKey(DATA))) { |
| 164 new List(r.serialization.rules.length).map((x) => []).toList(); | 175 throw new SerializationException("Missing $DATA entry, " |
| 165 var top = recursivelyFixUp(data, r, ruleData); | 176 "may mean this was written and read with different values " |
| 177 "of selfDescribing."); |
| 178 } |
| 179 // If we are self-describing, we should have separate rule and data |
| 180 // sections. If not, we assume that we have just the data at the top level. |
| 181 var rules = reader.selfDescribing ? data[RULES] : null; |
| 182 var actualData = reader.selfDescribing ? data[DATA] : data; |
| 183 reader.readRules(rules); |
| 184 var ruleData = new List(reader.rules.length).map((x) => []).toList(); |
| 185 var top = recursivelyFixUp(actualData, reader, ruleData); |
| 166 result["data"] = ruleData; | 186 result["data"] = ruleData; |
| 167 result["roots"] = [top]; | 187 result["roots"] = [top]; |
| 168 return result; | 188 return result; |
| 169 } | 189 } |
| 170 | 190 |
| 171 /** | 191 /** |
| 172 * Convert nested references in [data] into [Reference] objects. | 192 * Convert nested references in [data] into [Reference] objects. |
| 173 */ | 193 */ |
| 174 recursivelyFixUp(data, Reader r, List result) { | 194 recursivelyFixUp(input, Reader r, List result) { |
| 195 var data = input; |
| 175 if (isPrimitive(data)) { | 196 if (isPrimitive(data)) { |
| 176 result[r._primitiveRule().number].add(data); | 197 result[r._primitiveRule().number].add(data); |
| 177 return data; | 198 return data; |
| 178 } | 199 } |
| 179 var ruleNumber; | 200 var ruleNumber; |
| 201 // If we've added the rule number on as the last item in a list we have |
| 202 // to get rid of it or it will be interpreted as extra data. For a map |
| 203 // the library will be ok, but we need to get rid of the extra key before |
| 204 // the data is shown to the user, so we destructively modify. |
| 180 if (data is List) { | 205 if (data is List) { |
| 181 ruleNumber = data.removeLast(); | 206 ruleNumber = data.last; |
| 207 data = data.take(data.length -1); |
| 182 } else if (data is Map) { | 208 } else if (data is Map) { |
| 183 ruleNumber = data.remove(ruleIdentifier); | 209 ruleNumber = data.remove(RULE); |
| 184 } else { | 210 } else { |
| 185 throw new SerializationException("Invalid data format"); | 211 throw new SerializationException("Invalid data format"); |
| 186 } | 212 } |
| 187 var newData = mapValues(data, (x) => recursivelyFixUp(x, r, result)); | 213 // Do not use mappedBy or other lazy operations for this. They do not play |
| 214 // well with a function that destructively modifies its arguments. |
| 215 var newData = mapValues(data, (each) => recursivelyFixUp(each, r, result)); |
| 188 result[ruleNumber].add(newData); | 216 result[ruleNumber].add(newData); |
| 189 return new Reference(r, ruleNumber, result[ruleNumber].length - 1); | 217 return new Reference(r, ruleNumber, result[ruleNumber].length - 1); |
| 190 } | 218 } |
| 191 } | 219 } |
| 192 | 220 |
| 193 /** | 221 /** |
| 194 * Writes to a simple mostly-flat format. Details are subject to change. | 222 * Writes to a simple mostly-flat format. Details are subject to change. |
| 195 * Right now this produces a List containing null, num, and String. This is | 223 * Right now this produces a List containing null, num, and String. This is |
| 196 * more space-efficient than the map formats, but much less human-readable. | 224 * more space-efficient than the map formats, but much less human-readable. |
| 197 * Simple usage is to turn this into JSON for transmission. | 225 * Simple usage is to turn this into JSON for transmission. |
| (...skipping 230 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 428 return new Reference(r, a, b); | 456 return new Reference(r, a, b); |
| 429 } | 457 } |
| 430 } | 458 } |
| 431 | 459 |
| 432 /** Return the next element from the input. */ | 460 /** Return the next element from the input. */ |
| 433 _next(Iterator input) { | 461 _next(Iterator input) { |
| 434 input.moveNext(); | 462 input.moveNext(); |
| 435 return input.current; | 463 return input.current; |
| 436 } | 464 } |
| 437 } | 465 } |
| OLD | NEW |