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

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
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 }
OLDNEW
« no previous file with comments | « no previous file | pkg/serialization/lib/src/basic_rule.dart » ('j') | pkg/serialization/lib/src/basic_rule.dart » ('J')

Powered by Google App Engine
This is Rietveld 408576698