Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1230)

Side by Side Diff: pkg/serialization/lib/serialization.dart

Issue 11293283: Initial version of a serialization framework (Closed) Base URL: http://dart.googlecode.com/svn/branches/bleeding_edge/dart/
Patch Set: Created 8 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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
7 * [Serialization] is defined in terms of [SerializationRule]s and supports
8 * reading and writing to different formats.
9 *
10 * Setup
11 * =====
12 * A simple example of usage is
13 *
14 * var address = new Address();
15 * address.street = 'N 34th';
16 * address.city = 'Seattle';
17 * var serialization = new Serialization()
18 * ..addRuleFor(address);
justinfagnani 2012/11/20 02:05:30 I'm confused by this line here: does it att a rule
Alan Knight 2012/11/20 15:03:54 This is all just workaround for being able to use
19 * String output = serialization.write(address);
20 *
21 * This creates a new serialization and adds a rule for address objects. Right
justinfagnani 2012/11/20 02:05:30 Why is it called "serialization" and not "serializ
Alan Knight 2012/11/20 15:03:54 Because it's better to name objects for what they
22 * now it has to be passed an address instance because we can't write Address
23 * as a literal. Then we ask the Serialization to write the address and we get
24 * back a String which is a [JSON] representation of the state of it and related
25 * objects.
26 *
27 * The version above used reflection to automatically identify the public
28 * fields of the address object. We can also specify those fields explicitly.
29 *
30 * var serialization = new Serialization()
31 * ..addRuleFor(address,
justinfagnani 2012/11/20 02:05:30 "Rule" as a name here doesn't seem ideal to me. Ev
Alan Knight 2012/11/20 15:03:54 Hmm. Rule is a lot shorter than custom serializer.
32 * constructor: "create",
33 * constructorFields: ["number", "street"],
34 * fields: ["city"]);
35 *
36 * This rule still uses reflection to access the fields, but not to calculate
justinfagnani 2012/11/20 02:05:30 Is there a way to clarify the phrase "not to calcu
Alan Knight 2012/11/20 15:03:54 Done.
37 * them. We can also allow it to calculate the fields, but tell it to ignore
38 * some fields that we don't want used.
39 *
40 * var serialization = new Serialization()
41 * ..addRuleFor(address,
42 * constructor: "",
43 * excludeFields: ["other", "stuff"]);
44 *
45 * We can also use a completely non-reflective rule to serialize and
46 * de-serialize objects.
47 *
48 * addressToMap(a) => {"number" : a.number, "street" : a.street,
49 * "city" : a.city};
50 * createAddress(Map m) => new Address.create(m["number"], m["street"]);
51 * fillInAddress(Map m, Address a) => a.city = m["city"];
52 * var serialization = new Serialization()
53 * ..addRule(
54 * new ClosureToMapRule(anAddress.runtimeType,
justinfagnani 2012/11/20 02:05:30 When using many closures together I find it cleane
Alan Knight 2012/11/20 15:03:54 Yes. And the intent is that people can do that, wh
55 * addressToMap, createAddress, fillInAddress);
56 *
57 * Note that there are three different functions provided. The first one
justinfagnani 2012/11/20 02:05:30 "first one" -> "addressToMap"
Alan Knight 2012/11/20 15:03:54 Done.
58 * takes the fields we want serialized from the Address and puts them into a
59 * map. The second one creates a new address using a map like the one returned
justinfagnani 2012/11/20 02:05:30 "second one" -> "createAddress"
Alan Knight 2012/11/20 15:03:54 Done.
60 * by the first function. And the third one fills in any remaining state in the
61 * created object. At the moment, however, this is more likely to cause problems
justinfagnani 2012/11/20 02:05:30 Consider breaking this paragraph up, maybe right b
Alan Knight 2012/11/20 15:03:54 Done.
62 * with cycles. The problem is that before passing values to the user-supplied
63 * functions it has to inflate any references to be the real objects. Since it
64 * doesn't know which ones the creation function uses it has to inflate all of
65 * them. For example, consider a Node class with parent, leftChild, and
66 * rightChild, and the parent field was final and set in the constructor. When
67 * we inflate all of the values we will end up with a cycle and can't
68 * de-serialize. If we know which fields are used by the constructor we can
69 * inflate only those, which is what BasicRule does. We expect to make a richer
70 * API for rules not using reflection, but there's a tension between providing
71 * the serialization process with enough information and making it more work
72 * to specify.
73 *
74 * It is possible to give constructor fields values that aren't field names. If
justinfagnani 2012/11/20 02:05:30 I don't understand this paragraph.
Alan Knight 2012/11/20 15:03:54 Rewrote. It's a bit of a bells and whistles featur
75 * any value isn't a String, it will be treated as a constant. This allows you
76 * to provide constant values to a constructor that aren't obtained from fields.
77 *
78 * In some cases a non-constructor field should not be set using field
79 * access or a setter, but should be done by calling a method. For example, it
80 * may not be possible to set a List field "foo", and you need to call an
81 * addFoo() method for each entry in the list. In these cases, if you are using
82 * a BasicRule for the object you can call the specialTreatmentFor() method.
83 *
84 * s..addRuleFor(fooHolderInstance).specialTreatmentFor("foo",
justinfagnani 2012/11/20 02:05:30 I feel like "specialTreatementFor" is pretty long
Alan Knight 2012/11/20 15:03:54 I agree it's too long. But handleField() seems ver
85 * (parent, value) => for (var each in value) parent.addFoo(value));
86 *
87 * Writing
88 * =======
89 * To write objects, we use the write() methods. There are two variations.
90 *
91 * String output = serialization.write(someObject);
92 * List output = serialization.writeFlat(someObject);
93 *
94 * The first uses a representation in which objects are represented as maps
95 * keyed by field name, but in which references between objects have been
96 * converted into Reference objects. This is then encoded as a JSON string.
97 *
98 * The second representation holds all the objects as a List of simple types.
99 * For practical use you may want to convert that to a JSON or other encoded
100 * representation as well.
101 *
102 * Both representations are primarily intended as proofs of concept for
103 * different types of representation, and we expect to generalize that to a
104 * pluggable mechanism for different representations.
105 *
106 * Reading
107 * =======
108 * To read objects, the corresponding methods are [read] and [readFlat].
109 *
110 * List input = serialization.read(aString);
111 * List input = serialization.readFlat(aList);
justinfagnani 2012/11/20 02:05:30 Does this return a List of Lists? From the descrip
Alan Knight 2012/11/20 15:03:54 No, it returns a list of the roots that were writt
112 *
113 * There is also a convenience method for the case of reading a single object.
justinfagnani 2012/11/20 02:05:30 Are there corresponding methods to write multiple
Alan Knight 2012/11/20 15:03:54 See above.
114 *
115 * Object result = serialization.readOne(aString);
116 * Object result = serialization.readOneFlat(aString);
justinfagnani 2012/11/20 02:05:30 For multiple objects it would be great to have the
Alan Knight 2012/11/20 15:03:54 If the objects written/read are entirely independe
117 *
118 * When reading, the serialization instance doing the reading must be configured
119 * with compatible rules to the one doing the writing. It's possible for the
120 * rules to be different, but they need to be able to read the same
121 * representation. For most practical purposes right now they should be the
122 * same. The simplest way to achieve this is by having the serialization
123 * variable [selfDescribing] be true. In that case the rules themselves are also
124 * stored along with the serialized data, and can be read back on the receiving
125 * end. Note that this does not yet work for [ClosureToMapRule]. The
126 * [selfDescribing] variable is true by default.
127 *
128 * When reading, some object references should not be serialized, but should be
justinfagnani 2012/11/20 02:05:30 Why wouldn't this be done with a custom rule for t
Alan Knight 2012/11/20 15:03:54 It's not by type, but by instance. Or at least pot
129 * connected up to other instances on the receiving side. A notable example of
130 * this is when serialization rules have been stored. Instances of BasicRule
131 * take a [ClassMirror] in their constructor, and we cannot serialize those. So
132 * when we read the rules, we must provide a Map<String, Object> which maps from
133 * the simple name of classes we are interested in to a [ClassMirror]. This can
134 * be provided either in the [externalObjects] variable of the Serialization,
135 * or as an additional parameter to the reading methods.
136 *
137 * new Serialization()
138 * ..addRuleFor(new Person(), constructorFields: ["name"])
139 * ..externalObjects['Person'] = reflect(new Person()).type;
140 */
141 library serialization;
142
143 import 'src/mirrors_helpers.dart';
justinfagnani 2012/11/20 02:05:30 I think we're supposed to use package: urls even f
Alan Knight 2012/11/20 15:03:54 We weren't for stuff in the SDK, because the bots
144 import 'src/serialization_helpers.dart';
145 //import 'src/polyfill_identity_set.dart';
justinfagnani 2012/11/20 02:05:30 remove
Alan Knight 2012/11/20 15:03:54 Done.
146 import 'src/polyfill_identity_set.dart';
147 import 'dart:json';
148
149 part 'src/reader_writer.dart';
150 part 'src/serialization_rule.dart';
151 part 'src/basic_rule.dart';
152
153 /**
154 * This class defines a particular serialization scheme, in terms of
155 * [SerializationRule] instances, and supports reading and writing them.
156 * See library comment for examples of usage.
157 */
158 class Serialization {
159
160 /**
161 * The serialization is controlled by the list of Serialization rules. These
162 * are most commonly added via [addRuleFor].
163 */
164 List rules = [];
165
166 /**
167 * When reading, we may need to resolve references to existing objects in
168 * the system. The right action may not be to create a new instance of
169 * something, but rather to find an existing instance and connect to it.
170 * For example, if we have are serializing an Email message and it has a
171 * link to the owning account, it may not be appropriate to try and serialize
172 * the account. Instead we should just connect the de-serialized message
173 * object to the account object that already exists there.
174 */
175 Map<String, dynamic> externalObjects = {};
176
177 /**
178 * When we write out data using this serialization, should we also write
179 * out a description of the rules.
180 */
181 bool selfDescribing = true;
182
183 /**
184 * Creates a new serialization with a default set of rules for primitives
185 * and lists.
186 */
187 Serialization() {
188 _addDefaultRules();
189 }
190
191 /**
192 * Creates a new serialization with no default rules at all. The most common
193 * use for this is if we are reading self-describing serialized data and
194 * will populate the rules from that data.
195 */
196 Serialization.blank() { }
197
198 /**
199 * Create a [BasicRule] rule for the type of
200 * [instanceOfType]. Optionally
201 * allows specifying a [constructor] name, the list of [constructorFields],
202 * and the list of [fields] not used in the constructor. Returns the new
203 * rule.
204 *
205 * If the optional parameters aren't specified, the default constructor will
206 * be used, and the list of fields will be computed. Alternatively, you can
207 * omit [fields] and provide [excludeFields], which will then compute the
208 * list of fields specifically excluding those listed.
209 *
210 * The fields can be actual public fields, but can also be getter/setter
211 * pairs or getters whose value is provided in the constructor. For the
212 * [constructorFields] they can also be arbitrary objects. Anything that is
213 * not a String will be treated as a constant value to be used in any
214 * construction of these objects.
215 *
216 * If the list of fields is computed, fields from the superclass will be
217 * included. However, each subclass needs its own rule, since the constructors
218 * are not inherited, and so may need to be specified separately for each
219 * subclass.
220 */
221 // TODO(alanknight): Take a type rather than an instance. Issue 6282.
justinfagnani 2012/11/20 02:05:30 Can you still take a type and require that callers
Alan Knight 2012/11/20 15:03:54 Sadly, no. Checked to see if there was a bug for t
222 BasicRule addRuleFor(
223 instanceOfType,
224 {String constructor,
225 List constructorFields,
226 List<String> fields,
227 List<String> excludeFields}) {
228
229 var rule;
230 rule = new BasicRule(
231 turnInstanceIntoSomethingWeCanUse(
232 instanceOfType),
233 constructor, constructorFields, fields, excludeFields);
234 addRule(rule);
235 return rule;
236 }
237
238 /** By default we have rules for lists and primitives pre-populated. */
239 void _addDefaultRules() {
240 addRule(new PrimitiveRule());
241 addRule(new ListRule());
242 // Both these rules apply to lists, so unless otherwise indicated,
243 // it will always find the first one.
244 addRule(new ListRuleEssential());
245 }
246
247 /**
248 * Add a new SerializationRule [rule]. The addRuleFor method will probably
249 * handle most simple cases, but for adding an arbitrary rule, including
250 * a SerializationRule subclass which you have created, you can use this
251 * method.
252 */
253 void addRule(SerializationRule rule) {
254 rule.number = rules.length;
255 rules.add(rule);
256 }
257
258 /**
259 * This is the basic method to write out an object graph rooted at
260 * [object] and return the result. Right now this is hard-coded to return
261 * a String from a custom JSON format, but that is likely to change to be
262 * more pluggable in the near future.
263 */
264 String write(Object object) {
265 return newWriter().write(object);
266 }
267
268 /**
269 * Return a new [Writer] object for this serialization. This is useful if you
270 * want to do something more complex with the writer than just returning
271 * the final result.
272 */
273 Writer newWriter() => new Writer(this);
274
275 /**
276 * Write out the tree in a custom flat format, returning a list containing
277 * only "simple" types: num, String, and bool.
278 */
279 List writeFlat(Object object) {
280 return newWriter().writeFlat(object);
281 }
282
283 /**
284 * Read the serialized data from [input] and return a List of the root
285 * objects from the result. If there are objects that need to be resolved
286 * in the current context, they should be provided in [externals] as a
287 * Map from names to values. In particular, in the current implementation
288 * any class mirrors needed should be provided in [externals] using the
289 * class name as a key. In addition to the [externals] map provided here,
290 * values will be looked up in the [externalObjects] map.
291 */
292 List read(String input, [Map externals = const {}]) {
293 return newReader().read(input, externals);
294 }
295
296 /**
297 * In the most common case there is only a single root object to be read,
298 * and this method can be used to return just one object rather than
299 * a List. The [input] and [externals] parameters are the same as for the
300 * general [read] method.
301 */
302 Object readOne(String input, [Map externals = const {}]) {
303 return newReader().readOne(input, externals);
304 }
305
306 /**
307 * Return a new [Reader] object for this serialization. This is useful if
308 * you want to do something more complex with the reader than just returning
309 * the final result.
310 */
311 Reader newReader() => new Reader(this);
312
313 /**
314 * Return the list of SerializationRule that apply to [object]. For
315 * internal use, but public because it's used in testing.
316 */
317 List<SerializationRule> rulesFor(object) {
318 // This has a couple of edge cases.
319 // 1) The owning object may have indicated we should use a different
320 // rule than the default.
321 // 2) We may not have a rule, in which case we lazily create a BasicRule.
322 // 3) Rules are allowed to say mustBePrimary, meaning that they can be used
323 // iff no other rule was chosen first.
324 // TODO(alanknight): Can the mustBePrimary mechanism be removed or changed.
325 // It adds an order dependency to the rules, and is messy. Reconsider in the
326 // light of a more general mechanism for multiple rules per object.
327 // TODO(alanknight): Finding which rules apply seems likely to be a
328 // bottleneck, particularly with the current reflective implementation.
329 // Consider how to improve it. e.g. cache the list of rules by class. But
330 // be careful of issues like rules which have arbitrary predicates. Or
331 // consider having the arbitrary predicates be secondary to an initial
332 // class-based lookup mechanism.
333 var target, candidateRules;
334 if (object is DesignatedRuleForObject) {
335 target = object.target;
336 candidateRules = object.possibleRules(rules);
337 } else {
338 target = object;
339 candidateRules = rules;
340 }
341 List applicable = candidateRules.filter((each) => each.appliesTo(target));
342
343 if (applicable.isEmpty) {
344 return [addRuleFor(target)];
345 }
346
347 if (applicable.length == 1) return applicable;
348 var first = applicable[0];
349 var finalRules = applicable.filter(
350 (x) => !x.mustBePrimary || (x == first));
351
352 if (finalRules.isEmpty) throw new SerializationException(
353 'No valid rule found for object $object');
354 return finalRules;
355 }
356
357 /**
358 * Create a Serialization for serializing SerializationRules. This is used
359 * to save the rules in a self-describing format along with the data.
360 * If there are new rule classes created, they will need to be described
361 * here.
362 */
363 Serialization _ruleSerialization() {
364 // TODO(alanknight): There's an extensibility issue here with new rules.
365 // TODO(alanknight): How to handle rules with closures? They have to
366 // exist on the other side, but we might be able to hook them up by name,
367 // or we might just be able to validate that they're correctly set up
368 // on the other side.
369
370 // Make some bogus rule instances so we have something to feed rule creation
371 // and get their types. If only we had class literals implemented...
372 var closureRule = new ClosureToMapRule.stub([].runtimeType);
373 var basicRule = new BasicRule(reflect(null).type, '', [], [], []);
374
375 var meta = new Serialization()
376 ..selfDescribing = false
377 ..addRuleFor(new ListRule())
378 ..addRuleFor(new PrimitiveRule())
379 ..addRuleFor(new ListRuleEssential())
380 ..addRuleFor(basicRule,
381 constructorFields: ['typeWrapped',
382 'constructorName',
383 'constructorFields', 'regularFields', []],
384 fields: [])
385 ..addRule(new ClassMirrorRule());
386 meta.externalObjects = externalObjects;
387 return meta;
388 }
389 }
390
391 /**
392 * An exception class for errors during serialization.
393 */
394 class SerializationException implements Exception {
395 final String message;
396 const SerializationException([this.message]);
397 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698