Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 /** | |
| 6 * This provides a general-purpose serialization facility for Dart objects. A | |
|
Jennifer Messerly
2012/11/15 08:02:14
btw, this comment is lovely. So much content!
Alan Knight
2012/11/15 20:51:03
Thanks.
| |
| 7 * [Serialization] is defined in terms of [SerializationRule]s. A simple | |
| 8 * example of usage is | |
| 9 * var address = new Address(); | |
|
Jennifer Messerly
2012/11/15 08:02:14
general formatting: I think this might be easier t
Alan Knight
2012/11/15 20:51:03
Done.
| |
| 10 * address.street = 'N 34th'; | |
| 11 * address.city = 'Seattle'; | |
| 12 * var serialization = new Serialization() | |
| 13 * ..addRuleFor(address); | |
| 14 * String output = serialization.write(address); | |
| 15 * This creates a new serialization and adds a rule for address objects. Right | |
| 16 * now it has to be passed an address instance because we can't write Address | |
| 17 * as a literal. Then we ask the Serialization to write the address and we get | |
| 18 * back a String which is a JSON representation of the state of it and related | |
|
Jennifer Messerly
2012/11/15 08:02:14
this could link to JSON, e.g. [JSON]
and add an i
Alan Knight
2012/11/15 20:51:03
I'm not quite sure what you mean by the second par
Jennifer Messerly
2012/11/20 01:23:31
Oh I meant that you need to add "import 'dart:json
Alan Knight
2012/11/20 12:17:54
Hmm, even when I do that, it still seems to show a
| |
| 19 * objects. | |
| 20 * | |
| 21 * The version above used reflection to automatically identify the public | |
| 22 * fields of the address object. We can also specify those fields explicitly. | |
| 23 * var serialization = new Serialization() | |
| 24 * ..addRuleFor(address, | |
| 25 * constructor: "create", | |
| 26 * constructorFields: ["number", "street"], | |
| 27 * fields: ["city"]); | |
| 28 * This rule still uses reflection to access the fields, but not to calculate | |
| 29 * them. We can also allow it to calculate the fields, but tell it to ignore | |
| 30 * some fields that we don't want used. | |
| 31 * var serialization = new Serialization() | |
| 32 * ..addRuleFor(address, | |
| 33 * constructor: "", | |
| 34 * excludeFields: ["other", "stuff"]); | |
| 35 * | |
| 36 * We can also use a completely non-reflective rule to serialize and | |
| 37 * de-serialize objects. | |
| 38 * addressToMap(a) => {"number" : a.number, "street" : a.street, | |
| 39 * "city" : a.city}; | |
| 40 * createAddress(Map m) => new Address.create(m["number"], m["street"]); | |
| 41 * fillInAddress(Map m, Address a) => a.city = m["city"]; | |
| 42 * var serialization = new Serialization() | |
| 43 * ..addRule( | |
| 44 * new ClosureToMapRule(anAddress.runtimeType, | |
| 45 * addressToMap, createAddress, fillInAddress); | |
| 46 * Note that there are three different functions provided. The first one | |
| 47 * takes the fields we want serialized from the Address and puts them into a | |
| 48 * map. The second one creates a new address using a map like the one returned | |
| 49 * by the first function. And the third one fills in any remaining state in the | |
| 50 * created object. At the moment, however, this is more likely to cause problems | |
|
Jennifer Messerly
2012/11/15 08:02:14
This is an intriguing sentence. It makes me want t
Alan Knight
2012/11/15 20:51:03
Done.
| |
| 51 * with cycles. | |
| 52 * | |
| 53 * It is possible to give constructor fields values that aren't field names. If | |
| 54 * any value isn't a String, it will be treated as a constant. This allows you | |
| 55 * to provide constant values to a constructor that aren't obtained from fields. | |
| 56 * | |
| 57 * In some cases a non-constructor field cannot be properly set using field | |
|
Jennifer Messerly
2012/11/15 08:02:14
suggestion: remove "properly" ... it is too easy t
Alan Knight
2012/11/15 20:51:03
Done.
| |
| 58 * access or a setter, and you need to call a method. For example, it may not | |
| 59 * be possible to set a List field "foo", and you need to call an addFoo() | |
| 60 * method for each entry in the list. In these cases, if you are using a | |
| 61 * BasicRule for the object you can call the specialTreatmentFor() method. | |
| 62 * s..addRuleFor(fooHolderInstance).specialTreatmentFor("foo", | |
| 63 * (parent, value) => for (var each in value) parent.addFoo(value)); | |
| 64 * | |
| 65 * To read and write objects, we use the read() and write() methods. There are | |
| 66 * currently two variations. | |
|
Jennifer Messerly
2012/11/15 08:02:14
perhaps just say "There are two variations", then
Alan Knight
2012/11/15 20:51:03
Done. I think I keep wanting to apologize for the
| |
| 67 * String output = serialization.write(someObject); | |
| 68 * List output = serialization.writeFlat(someObject); | |
| 69 * The first uses a representation in which objects are represented as maps | |
| 70 * keyed by field name, but in which references between objects have been | |
| 71 * converted into Reference objects. This is then encoded as a JSON string. | |
| 72 * | |
| 73 * The second representation holds all the objects as a List of simple types. | |
| 74 * For practical use you may want to convert that to a JSON or other encoded | |
| 75 * representation as well. | |
| 76 * | |
| 77 * Both representations as primarily intended as proofs of concept for different | |
|
Jennifer Messerly
2012/11/15 08:02:14
as primarily -> are primarily
Alan Knight
2012/11/15 20:51:03
Done.
| |
| 78 * types of representation, and we expect to generalize that to a pluggable | |
| 79 * mechanism for different representations. | |
| 80 * | |
| 81 * To read objects, the corresponding methods are [read] and [readFlat]. | |
|
Jennifer Messerly
2012/11/15 08:02:14
I think it would be helpful to use the markdown He
Alan Knight
2012/11/15 20:51:03
Done.
| |
| 82 * List input = serialization.read(aString); | |
| 83 * List input = serialization.readFlat(aList); | |
| 84 * There is also a convenience method for the case of reading a single object. | |
| 85 * Object result = serialization.readOne(aString); | |
| 86 * Object result = serialization.readOneFlat(aString); | |
| 87 * | |
| 88 * When reading, the serialization instance doing the reading must be configured | |
| 89 * with compatible rules to the one doing the writing. It's possible for the | |
| 90 * rules to be different, but they need to be able to read the same | |
| 91 * representation. For most practical purposes right now they should be the | |
| 92 * same. The simplest way to achieve this is by having the serialization | |
| 93 * variable [selfDescribing] be true. In that case the rules themselves are also | |
| 94 * stored along with the serialized data, and can be read back on the receiving | |
| 95 * end. Note that this does not yet work for ClosureToMapRule. The | |
| 96 * [selfDescribing] variable is true by default. | |
| 97 * | |
| 98 * When reading, some object references should not be serialized, but should be | |
| 99 * connected up to other instances on the receiving side. A notable example of | |
| 100 * this is when serialization rules have been stored. Instances of BasicRule | |
| 101 * take a ClassMirror in their constructor, and we cannot serialize those. So | |
| 102 * when we read the rules, we must provide a Map<String, Object> which maps from | |
| 103 * the simple name of classes we are interested in to a ClassMirror. This can | |
|
Jennifer Messerly
2012/11/15 08:02:14
would be nice to link to classmirror, if possible
Alan Knight
2012/11/15 20:51:03
Done.
| |
| 104 * be provided either in the [externalObjects] variable of the Serialization, | |
| 105 * or as an additional parameter to the reading methods. | |
| 106 * new Serialization() | |
| 107 * ..addRuleFor(new Person(), constructorFields: ["name"]) | |
| 108 * ..externalObjects['Person'] = reflect(new Person()).type; | |
| 109 */ | |
| 110 | |
|
Jennifer Messerly
2012/11/15 08:02:14
looks like extra newline here
Alan Knight
2012/11/15 20:51:03
Done.
| |
| 111 library serialization; | |
| 112 | |
| 113 import 'src/mirrors_helpers.dart'; | |
| 114 import 'src/serialization_helpers.dart'; | |
| 115 import 'src/polyfill_identity_set.dart'; | |
| 116 import 'dart:json'; | |
| 117 | |
| 118 part 'src/reader_writer.dart'; | |
| 119 part 'src/serialization_rule.dart'; | |
| 120 part 'src/basic_rule.dart'; | |
| 121 | |
| 122 /** | |
| 123 * This class defines a particular serialization scheme, in terms of | |
| 124 * [SerializationRule] instances, and supports reading and writing them. | |
| 125 * See library comment for examples of usage. | |
| 126 */ | |
| 127 class Serialization { | |
|
Jennifer Messerly
2012/11/15 08:02:14
random idea, could we call this Serializer?
Alan Knight
2012/11/15 20:51:03
I really prefer calling things nouns rather than v
| |
| 128 | |
| 129 /** | |
| 130 * The serialization is controlled by the list of Serialization rules. These | |
| 131 * are most commonly added via [addRuleFor]. | |
| 132 */ | |
| 133 List rules = []; | |
| 134 | |
| 135 /** | |
| 136 * When reading, we may need to resolve references to existing objects in | |
|
Jennifer Messerly
2012/11/15 08:02:14
The part about class mirrors is not totally clear
Alan Knight
2012/11/15 20:51:03
We can't construct them because they're an abstrac
| |
| 137 * the system. Notably, when reading rule descriptions in a self-describing | |
| 138 * format we can't construct class mirrors, so we rely on the external objects | |
| 139 * giving us the ones we need. But this can be used for any object. | |
| 140 */ | |
| 141 Map<String, dynamic> externalObjects = new Map(); | |
|
Jennifer Messerly
2012/11/15 08:02:14
if you use "{}" instead of new Map, I think the ke
Alan Knight
2012/11/15 20:51:03
Done.
| |
| 142 | |
| 143 /** | |
| 144 * When we write out data using this serialization, should we also write | |
| 145 * out a description of the rules. | |
| 146 */ | |
| 147 bool selfDescribing = true; | |
| 148 | |
| 149 /** | |
| 150 * Creates a new serialization with a default set of rules for primitives | |
| 151 * and lists. | |
| 152 */ | |
| 153 Serialization() { | |
| 154 _addDefaultRules(); | |
| 155 } | |
| 156 | |
| 157 /** | |
| 158 * Creates a new serialization with no default rules at all. The most common | |
| 159 * use for this is if we are reading self-describing serialized data and | |
| 160 * will populate the rules from that data. | |
| 161 */ | |
| 162 Serialization.noDefaultRules() { } | |
|
Jennifer Messerly
2012/11/15 08:02:14
Serialization.blank?
Alan Knight
2012/11/15 20:51:03
Yes, much better. Done.
| |
| 163 | |
| 164 /** | |
| 165 * Create a [BasicRule] rule for the type of | |
| 166 * [someInstanceThatWeHaveToPassInBecauseWeCantUseLiteralTypes]. Optionally | |
| 167 * allows specifying a [constructor] name, the list of [constructorFields], | |
| 168 * and the list of [fields] not used in the constructor. Returns the new | |
| 169 * rule. | |
| 170 * | |
| 171 * If the optional parameters aren't specified, the default constructor will | |
| 172 * be used, and the list of fields will be computed. Alternatively, you can | |
| 173 * omit [fields] and provide [excludeFields], which will then compute the | |
| 174 * list of fields specifically excluding those listed. | |
| 175 * | |
| 176 * The fields can be actual public fields, but can also be getter/setter | |
| 177 * pairs or getters whose value is provided in the constructor. For the | |
| 178 * [constructorFields] they can also be arbitrary objects. Anything that is | |
| 179 * not a String will be treated as a constant value to be used in any | |
| 180 * construction of these objects. | |
| 181 * | |
| 182 * If the list of fields is computed, fields from the superclass will be | |
| 183 * included. However, each subclass needs its own rule, since the constructors | |
| 184 * are not inherited, and so may need to be specified separately for each | |
| 185 * subclass. | |
| 186 */ | |
| 187 // TODO(alanknight): Take a type rather than an instance. Issue 6282. | |
| 188 BasicRule addRuleFor( | |
| 189 someInstanceThatWeHaveToPassInBecauseWeCantUseLiteralTypes, | |
|
Jennifer Messerly
2012/11/15 08:02:14
haha :)
as funny as this is, I would give it a no
Alan Knight
2012/11/15 20:51:03
Awww. Done.
| |
| 190 {String constructor, | |
| 191 List constructorFields, | |
| 192 List<String> fields, | |
| 193 List<String> excludeFields}) { | |
| 194 | |
| 195 var rule; | |
| 196 rule = new BasicRule( | |
| 197 turnInstanceIntoSomethingWeCanUse( | |
| 198 someInstanceThatWeHaveToPassInBecauseWeCantUseLiteralTypes), | |
| 199 constructor, constructorFields, fields, excludeFields); | |
| 200 addRule(rule); | |
| 201 return rule; | |
| 202 } | |
| 203 | |
| 204 /** By default we have rules for lists and primitives pre-populated. */ | |
| 205 void _addDefaultRules() { | |
| 206 addRule(new PrimitiveRule()); | |
| 207 addRule(new ListRule()); | |
| 208 // Both these rules apply to lists, so unless otherwise indicated, | |
| 209 // it will always find the first one. | |
| 210 addRule(new ListRuleEssential()); | |
| 211 } | |
| 212 | |
| 213 /** | |
| 214 * Add a new SerializationRule [rule]. The addRuleFor method will probably | |
| 215 * handle most simple cases, but for adding an arbitrary rule, including | |
| 216 * a SerializationRule subclass which you have created, you can use this | |
| 217 * method. | |
| 218 */ | |
| 219 void addRule(SerializationRule rule) { | |
| 220 rule.number = rules.length; | |
| 221 rules.add(rule); | |
| 222 } | |
| 223 | |
| 224 /** | |
| 225 * This is the basic method to write out an object graph rooted at | |
| 226 * [object] and return the result. Right now this is hard-coded to return | |
| 227 * a String from a custom JSON format, but that is likely to change to be | |
| 228 * more pluggable in the near future. | |
| 229 */ | |
| 230 String write(Object object) { | |
| 231 return newWriter().write(object); | |
| 232 } | |
| 233 | |
| 234 /** | |
| 235 * Return a new [Writer] object for this serialization. This is useful if you | |
| 236 * want to do something more complex with the writer than just returning | |
| 237 * the final result. | |
| 238 */ | |
| 239 Writer newWriter() => new Writer(this); | |
| 240 | |
| 241 /** | |
| 242 * Write out the tree in a custom flat format, returning a list containing | |
| 243 * only "simple" types: num, String, and bool. | |
| 244 */ | |
| 245 List writeFlat(Object object) { | |
| 246 return newWriter().writeFlat(object); | |
| 247 } | |
| 248 | |
| 249 /** | |
| 250 * Read the serialized data from [input] and return a List of the root | |
| 251 * objects from the result. If there are objects that need to be resolved | |
| 252 * in the current context, they should be provided in [externals] as a | |
| 253 * Map from names to values. In particular, in the current implementation | |
| 254 * any class mirrors needed should be provided in [externals] using the | |
| 255 * class name as a key. In addition to the [externals] map provided here, | |
| 256 * values will be looked up in the [externalObjects] map. | |
| 257 */ | |
| 258 List read(String input, [Map externals = const {}]) { | |
| 259 return newReader().read(input, externals); | |
| 260 } | |
| 261 | |
| 262 /** | |
| 263 * In the most common case there is only a single root object to be read, | |
| 264 * and this method can be used to return just one object rather than | |
| 265 * a List. The [input] and [externals] parameters are the same as for the | |
| 266 * general [read] method. | |
| 267 */ | |
| 268 Object readOne(String input, [Map externals = const {}]) { | |
| 269 return newReader().readOne(input, externals); | |
| 270 } | |
| 271 | |
| 272 /** | |
| 273 * Return a new [Reader] object for this serialization. This is useful if | |
| 274 * you want to do something more complex with the reader than just returning | |
| 275 * the final result. | |
| 276 */ | |
| 277 Reader newReader() => new Reader(this); | |
| 278 | |
| 279 /** | |
| 280 * Return the list of SerializationRule that apply to [object]. For | |
| 281 * internal use, but public because it's used in testing. | |
| 282 */ | |
| 283 List<SerializationRule> rulesFor(object) { | |
| 284 // This has a couple of edge cases. | |
| 285 // 1) The owning object may have indicated we should use a different | |
| 286 // rule than the default. | |
| 287 // 2) We may not have a rule, in which case we lazily create a BasicRule. | |
| 288 // 3) Rules are allowed to say mustBePrimary, meaning that they can be used | |
| 289 // iff no other rule was chosen first. | |
| 290 // TODO(alanknight): Can the mustBePrimary mechanism be removed or changed. | |
| 291 // It adds an order dependency to the rules, and is messy. Reconsider in the | |
| 292 // light of a more general mechanism for multiple rules per object. | |
| 293 // TODO(alanknight): Finding which rules apply seems likely to be a | |
| 294 // bottleneck, particularly with the current reflective implementation. | |
| 295 // Consider how to improve it. e.g. cache the list of rules by class. But | |
| 296 // be careful of issues like rules which have arbitrary predicates. Or | |
| 297 // consider having the arbitrary predicates be secondary to an initial | |
|
Jennifer Messerly
2012/11/15 08:02:14
this seems reasonable to me.
Alan Knight
2012/11/15 20:51:03
OK
| |
| 298 // class-based lookup mechanism. | |
| 299 var target, candidateRules; | |
| 300 if (object is DesignatedRuleForObject) { | |
| 301 target = object.target; | |
| 302 candidateRules = object.possibleRules(rules); | |
| 303 } else { | |
| 304 target = object; | |
| 305 candidateRules = rules; | |
| 306 } | |
| 307 List applicable = candidateRules.filter((each) => each.appliesTo(target)); | |
| 308 | |
| 309 if (applicable.isEmpty) { | |
| 310 var newRule = addRuleFor(target); | |
| 311 return [newRule]; | |
|
Jennifer Messerly
2012/11/15 08:02:14
return [addRuleFor(target)]; ?
Alan Knight
2012/11/15 20:51:03
Done. I often find myself pulling stuff apart into
| |
| 312 } | |
| 313 | |
| 314 if (applicable.length == 1) return applicable; | |
| 315 var finalRules = applicable.filter( | |
| 316 (x) => !(x != applicable[0] && x.mustBePrimary)); | |
|
Jennifer Messerly
2012/11/15 08:02:14
probably clearer as
!x.mustBePrimary || x == appl
Alan Knight
2012/11/15 20:51:03
If something has mustBePrimary, then it has to be
| |
| 317 | |
| 318 if (finalRules.isEmpty) throw new SerializationException( | |
| 319 'No valid rule found for object $object'); | |
| 320 return finalRules; | |
| 321 } | |
| 322 | |
| 323 /** | |
| 324 * Create a Serialization for serializing SerializationRules. This is used | |
| 325 * to save the rules in a self-describing format along with the data. | |
| 326 * If there are new rule classes created, they will need to be described | |
| 327 * here. | |
| 328 */ | |
| 329 Serialization _ruleSerialization() { | |
| 330 // TODO(alanknight): There's an extensibility issue here with new rules. | |
| 331 // TODO(alanknight): How to handle rules with closures? They have to | |
| 332 // exist on the other side, but we might be able to hook them up by name, | |
| 333 // or we might just be able to validate that they're correctly set up | |
| 334 // on the other side. | |
| 335 | |
| 336 // Make some bogus rule instances so we have something to feed rule creation | |
| 337 // and get their types. If only we had class literals implemented... | |
| 338 var closureRule = new ClosureToMapRule.stub([].runtimeType); | |
| 339 var basicRule = new BasicRule(reflect(null).type, '', [], [], []); | |
| 340 | |
| 341 var meta = new Serialization() | |
| 342 ..selfDescribing = false | |
| 343 ..addRuleFor(new ListRule()) | |
| 344 ..addRuleFor(new PrimitiveRule()) | |
| 345 ..addRuleFor(new ListRuleEssential()) | |
| 346 ..addRuleFor(basicRule, | |
| 347 constructorFields: ['typeWrapped', | |
| 348 'constructorName', | |
| 349 'constructorFields', 'regularFields', []], | |
| 350 fields: []) | |
| 351 ..addRule(new ClassMirrorRule()); | |
| 352 meta.externalObjects = externalObjects; | |
| 353 return meta; | |
| 354 } | |
| 355 } | |
| 356 | |
| 357 /** | |
| 358 * An exception class for errors during serialization. | |
| 359 */ | |
| 360 class SerializationException implements Exception { | |
| 361 final String message; | |
| 362 const SerializationException([this.message]); | |
| 363 } | |
| OLD | NEW |