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

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

Powered by Google App Engine
This is Rietveld 408576698