1 /**
2 	Generic serialization framework.
3 
4 	This module provides general means for implementing (de-)serialization with
5 	a standardized behavior.
6 
7 	Supported_types:
8 		The following rules are applied in order when serializing or
9 		deserializing a certain type:
10 
11 		$(OL
12 			$(LI An $(D enum) type is serialized as its raw value, except if
13 				$(D @byName) is used, in which case the name of the enum value
14 				is serialized.)
15 			$(LI Any type that is specifically supported by the serializer
16 				is directly serialized. For example, the BSON serializer
17 				supports $(D BsonObjectID) directly.)
18 			$(LI Arrays and tuples ($(D std.typecons.Tuple)) are serialized
19 				using the array serialization functions where each element is
20 				serialized again according to these rules.)
21 			$(LI Associative arrays are serialized similar to arrays. The key
22 				type of the AA must satisfy the $(D isStringSerializable) trait
23 				and will always be serialized as a string.)
24 			$(LI Any $(D Nullable!T) will be serialized as either $(D null), or
25 				as the contained value (subject to these rules again).)
26 			$(LI Types satisfying the $(D isPolicySerializable) trait for the
27 				supplied $(D Policy) will be serialized as the value returned
28 				by the policy $(D toRepresentation) function (again subject to
29 				these rules).)
30 			$(LI Types satisfying the $(D isCustomSerializable) trait will be
31 				serialized as the value returned by their $(D toRepresentation)
32 				method (again subject to these rules).)
33 			$(LI Types satisfying the $(D isISOExtSerializable) trait will be
34 				serialized as a string, as returned by their $(D toISOExtString)
35 				method. This causes types such as $(D SysTime) to be serialized
36 				as strings.)
37 			$(LI Types satisfying the $(D isStringSerializable) trait will be
38 				serialized as a string, as returned by their $(D toString)
39 				method.)
40 			$(LI Struct and class types by default will be serialized as
41 				associative arrays, where the key is the name of the
42 				corresponding field (can be overridden using the $(D @name)
43 				attribute). If the struct/class is annotated with $(D @asArray),
44 				it will instead be serialized as a flat array of values in the
45 				order of declaration. Null class references will be serialized
46 				as $(D null).)
47 			$(LI Pointer types will be serialized as either $(D null), or as
48 				the value they point to.)
49 			$(LI Built-in integers and floating point values, as well as
50 				boolean values will be converted to strings, if the serializer
51 				doesn't support them directly.)
52 		)
53 
54 		Note that no aliasing detection is performed, so that pointers, class
55 		references and arrays referencing the same memory will be serialized
56 		as multiple copies. When in turn deserializing the data, they will also
57 		end up as separate copies in memory.
58 
59 	Serializer_implementation:
60 		Serializers are implemented in terms of a struct with template methods that
61 		get called by the serialization framework:
62 
63 		---
64 		struct ExampleSerializer {
65 			enum isSupportedValueType(T) = is(T == string) || is(T == typeof(null));
66 
67 			// serialization
68 			auto getSerializedResult();
69 			void beginWriteDictionary(T)();
70 			void endWriteDictionary(T)();
71 			void beginWriteDictionaryEntry(T)(string name);
72 			void endWriteDictionaryEntry(T)(string name);
73 			void beginWriteArray(T)(size_t length);
74 			void endWriteArray(T)();
75 			void beginWriteArrayEntry(T)(size_t index);
76 			void endWriteArrayEntry(T)(size_t index);
77 			void writeValue(T)(T value);
78 
79 			// deserialization
80 			void readDictionary(T)(scope void delegate(string) entry_callback);
81 			void readArray(T)(scope void delegate(size_t) size_callback, scope void delegate() entry_callback);
82 			T readValue(T)();
83 			bool tryReadNull();
84 		}
85 		---
86 
87 	Copyright: © 2013-2014 rejectedsoftware e.K.
88 	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
89 	Authors: Sönke Ludwig
90 */
91 module vson.serialization;
92 
93 import vson.meta.traits;
94 import vson.meta.uda;
95 
96 import std.array : Appender, appender;
97 import std.conv : to;
98 import std.exception : enforce;
99 import std.traits;
100 import std.typetuple;
101 
102 
103 /**
104 	Serializes a value with the given serializer.
105 
106 	The serializer must have a value result for the first form
107 	to work. Otherwise, use the range based form.
108 
109 	See_Also: vson.json.JsonSerializer, vson.json.JsonStringSerializer, vson.bson.BsonSerializer
110 */
111 auto serialize(Serializer, T, ARGS...)(T value, ARGS args)
112 {
113 	auto serializer = Serializer(args);
114 	serialize(serializer, value);
115 	return serializer.getSerializedResult();
116 }
117 /// ditto
118 void serialize(Serializer, T)(ref Serializer serializer, T value)
119 {
120 	serializeImpl!(Serializer, DefaultPolicy, T)(serializer, value);
121 }
122 
123 ///
124 unittest {
125 	import vson.json;
126 
127 	struct Test {
128 		int value;
129 		string text;
130 	}
131 
132 	Test test;
133 	test.value = 12;
134 	test.text = "Hello";
135 
136 	Json serialized = serialize!JsonSerializer(test);
137 	assert(serialized.value.get!int == 12);
138 	assert(serialized.text.get!string == "Hello");
139 }
140 
141 unittest {
142 	import vson.json;
143 
144 	// Make sure that immutable(char[]) works just like string
145 	// (i.e., immutable(char)[]).
146 	immutable key = "answer";
147 	auto ints = [key: 42];
148 	auto serialized = serialize!JsonSerializer(ints);
149 	assert(serialized[key].get!int == 42);
150 }
151 
152 /**
153 	Serializes a value with the given serializer, representing values according to $(D Policy) when possible.
154 
155 	The serializer must have a value result for the first form
156 	to work. Otherwise, use the range based form.
157 
158 	See_Also: vson.json.JsonSerializer, vson.json.JsonStringSerializer, vson.bson.BsonSerializer
159 */
160 auto serializeWithPolicy(Serializer, alias Policy, T, ARGS...)(T value, ARGS args)
161 {
162 	auto serializer = Serializer(args);
163 	serializeWithPolicy!(Serializer, Policy)(serializer, value);
164 	return serializer.getSerializedResult();
165 }
166 /// ditto
167 void serializeWithPolicy(Serializer, alias Policy, T)(ref Serializer serializer, T value)
168 {
169 	serializeImpl!(Serializer, Policy, T)(serializer, value);
170 }
171 ///
172 version (unittest)
173 {
174 	template SizePol(T)
175 	{
176 		import std.conv;
177 		import std.array;
178 
179 		string toRepresentation(T value) {
180 			return to!string(value.x) ~ "x" ~ to!string(value.y);
181 		}
182 		
183 		T fromRepresentation(string value) {
184 			string[] fields = value.split('x');
185 			alias fieldT = typeof(T.x);
186 			auto x = to!fieldT(fields[0]);
187 			auto y = to!fieldT(fields[1]);
188 			return T(x, y);
189 		}
190 	}
191 }
192 ///
193 unittest {
194 	import vson.json;
195 
196 	static struct SizeI {
197 		int x;
198 		int y;
199 	}
200 	SizeI sizeI = SizeI(1,2);
201 	Json serializedI = serializeWithPolicy!(JsonSerializer, SizePol)(sizeI);
202 	assert(serializedI.get!string == "1x2");
203 
204 	static struct SizeF {
205 		float x;
206 		float y;
207 	}
208 	SizeF sizeF = SizeF(0.1f,0.2f);
209 	Json serializedF = serializeWithPolicy!(JsonSerializer, SizePol)(sizeF);
210 	assert(serializedF.get!string == "0.1x0.2");
211 }
212 
213 /**
214 	Deserializes and returns a serialized value.
215 
216 	serialized_data can be either an input range or a value containing
217 	the serialized data, depending on the type of serializer used.
218 
219 	See_Also: vson.json.JsonSerializer, vson.json.JsonStringSerializer, vson.bson.BsonSerializer
220 */
221 T deserialize(Serializer, T, ARGS...)(ARGS args)
222 {
223 	auto deserializer = Serializer(args);
224 	return deserializeImpl!(T, DefaultPolicy, Serializer)(deserializer);
225 }
226 
227 ///
228 unittest {
229 	import vson.json;
230 
231 	struct Test {
232 		int value;
233 		string text;
234 	}
235 
236 	Json serialized = Json.emptyObject;
237 	serialized.value = 12;
238 	serialized.text = "Hello";
239 
240 	Test test = deserialize!(JsonSerializer, Test)(serialized);
241 	assert(test.value == 12);
242 	assert(test.text == "Hello");
243 }
244 
245 /**
246 	Deserializes and returns a serialized value, interpreting values according to $(D Policy) when possible.
247 
248 	serialized_data can be either an input range or a value containing
249 	the serialized data, depending on the type of serializer used.
250 
251 	See_Also: vson.json.JsonSerializer, vson.json.JsonStringSerializer, vson.bson.BsonSerializer
252 */
253 T deserializeWithPolicy(Serializer, alias Policy, T, ARGS...)(ARGS args)
254 {
255 	auto deserializer = Serializer(args);
256 	return deserializeImpl!(T, Policy, Serializer)(deserializer);
257 }
258 
259 ///
260 unittest {
261 	import vson.json;
262 	
263 	static struct SizeI {
264 		int x;
265 		int y;
266 	}
267 
268 	Json serializedI = "1x2";
269 	SizeI sizeI = deserializeWithPolicy!(JsonSerializer, SizePol, SizeI)(serializedI);
270 	assert(sizeI.x == 1);
271 	assert(sizeI.y == 2);
272 	
273 	static struct SizeF {
274 		float x;
275 		float y;
276 	}
277 	Json serializedF = "0.1x0.2";
278 	SizeF sizeF = deserializeWithPolicy!(JsonSerializer, SizePol, SizeF)(serializedF);
279 	assert(sizeF.x == 0.1f);
280 	assert(sizeF.y == 0.2f);
281 }
282 
283 private void serializeImpl(Serializer, alias Policy, T, ATTRIBUTES...)(ref Serializer serializer, T value)
284 {
285 	import std.typecons : Nullable, Tuple, tuple;
286 
287 	static assert(Serializer.isSupportedValueType!string, "All serializers must support string values.");
288 	static assert(Serializer.isSupportedValueType!(typeof(null)), "All serializers must support null values.");
289 
290 	alias TU = Unqual!T;
291 
292 	static if (is(TU == enum)) {
293 		static if (hasAttributeL!(ByNameAttribute, ATTRIBUTES)) {
294 			serializeImpl!(Serializer, Policy, string)(serializer, value.to!string());
295 		} else {
296 			serializeImpl!(Serializer, Policy, OriginalType!TU)(serializer, cast(OriginalType!TU)value);
297 		}
298 	} else static if (Serializer.isSupportedValueType!TU) {
299 		static if (is(TU == typeof(null))) serializer.writeValue!TU(null);
300 		else serializer.writeValue!TU(value);
301 	} else static if (/*isInstanceOf!(Tuple, TU)*/is(T == Tuple!TPS, TPS...)) {
302 		static if (TU.Types.length == 1) {
303 			serializeImpl!(Serializer, Policy, typeof(value[0]), ATTRIBUTES)(serializer, value[0]);
304 		} else {
305 			serializer.beginWriteArray!TU(value.length);
306 			foreach (i, TV; T.Types) {
307 				serializer.beginWriteArrayEntry!TV(i);
308 				serializeImpl!(Serializer, Policy, TV, ATTRIBUTES)(serializer, value[i]);
309 				serializer.endWriteArrayEntry!TV(i);
310 			}
311 			serializer.endWriteArray!TU();
312 		}
313 	} else static if (isArray!TU) {
314 		alias TV = typeof(value[0]);
315 		serializer.beginWriteArray!TU(value.length);
316 		foreach (i, ref el; value) {
317 			serializer.beginWriteArrayEntry!TV(i);
318 			serializeImpl!(Serializer, Policy, TV, ATTRIBUTES)(serializer, el);
319 			serializer.endWriteArrayEntry!TV(i);
320 		}
321 		serializer.endWriteArray!TU();
322 	} else static if (isAssociativeArray!TU) {
323 		alias TK = KeyType!TU;
324 		alias TV = ValueType!TU;
325 		static if (__traits(compiles, serializer.beginWriteDictionary!TU(0))) {
326 			auto nfields = value.length;
327 			serializer.beginWriteDictionary!TU(nfields);
328 		} else {
329 			serializer.beginWriteDictionary!TU();
330 		}
331 		foreach (key, ref el; value) {
332 			string keyname;
333 			static if (is(TK : string)) keyname = key;
334 			else static if (is(TK : real) || is(TK : long) || is(TK == enum)) keyname = key.to!string;
335 			else static if (isStringSerializable!TK) keyname = key.toString();
336 			else static assert(false, "Associative array keys must be strings, numbers, enums, or have toString/fromString methods.");
337 			serializer.beginWriteDictionaryEntry!TV(keyname);
338 			serializeImpl!(Serializer, Policy, TV, ATTRIBUTES)(serializer, el);
339 			serializer.endWriteDictionaryEntry!TV(keyname);
340 		}
341 		static if (__traits(compiles, serializer.endWriteDictionary!TU(0))) {
342 			serializer.endWriteDictionary!TU(nfields);
343 		} else {
344 			serializer.endWriteDictionary!TU();
345 		}
346 	} else static if (/*isInstanceOf!(Nullable, TU)*/is(T == Nullable!TPS, TPS...)) {
347 		if (value.isNull()) serializeImpl!(Serializer, Policy, typeof(null))(serializer, null);
348 		else serializeImpl!(Serializer, Policy, typeof(value.get()), ATTRIBUTES)(serializer, value.get());
349 	} else static if (isPolicySerializable!(Policy, TU)) {
350 		alias CustomType = typeof(Policy!TU.toRepresentation(TU.init));
351 		serializeImpl!(Serializer, Policy, CustomType, ATTRIBUTES)(serializer, Policy!TU.toRepresentation(value));
352 	} else static if (isCustomSerializable!TU) {
353 		alias CustomType = typeof(T.init.toRepresentation());
354 		serializeImpl!(Serializer, Policy, CustomType, ATTRIBUTES)(serializer, value.toRepresentation());
355 	} else static if (isISOExtStringSerializable!TU) {
356 		serializer.writeValue(value.toISOExtString());
357 	} else static if (isStringSerializable!TU) {
358 		serializer.writeValue(value.toString());
359 	} else static if (is(TU == struct) || is(TU == class)) {
360 		static if (!hasSerializableFields!TU)
361 			pragma(msg, "Serializing composite type "~T.stringof~" which has no serializable fields");
362 		static if (is(TU == class)) {
363 			if (value is null) {
364 				serializeImpl!(Serializer, Policy, typeof(null))(serializer, null);
365 				return;
366 			}
367 		}
368 		static if (hasAttributeL!(AsArrayAttribute, ATTRIBUTES)) {
369 			enum nfields = getExpandedFieldCount!(TU, SerializableFields!TU);
370 			serializer.beginWriteArray!TU(nfields);
371 			foreach (mname; SerializableFields!TU) {
372 				alias TMS = TypeTuple!(typeof(__traits(getMember, value, mname)));
373 				foreach (j, TM; TMS) {
374 					alias TA = TypeTuple!(__traits(getAttributes, TypeTuple!(__traits(getMember, T, mname))[j]));
375 					serializer.beginWriteArrayEntry!TM(j);
376 					serializeImpl!(Serializer, Policy, TM, TA)(serializer, tuple(__traits(getMember, value, mname))[j]);
377 					serializer.endWriteArrayEntry!TM(j);
378 				}
379 			}
380 			serializer.endWriteArray!TU();
381 		} else {
382 			static if (__traits(compiles, serializer.beginWriteDictionary!TU(0))) {
383 				enum nfields = getExpandedFieldCount!(TU, SerializableFields!TU);
384 				serializer.beginWriteDictionary!TU(nfields);
385 			} else {
386 				serializer.beginWriteDictionary!TU();
387 			}
388 			foreach (mname; SerializableFields!TU) {
389 				alias TM = TypeTuple!(typeof(__traits(getMember, value, mname)));
390 				static if (TM.length == 1) {
391 					alias TA = TypeTuple!(__traits(getAttributes, __traits(getMember, T, mname)));
392 					enum name = getAttribute!(TU, mname, NameAttribute)(NameAttribute(underscoreStrip(mname))).name;
393 					auto vt = __traits(getMember, value, mname);
394 					serializer.beginWriteDictionaryEntry!(typeof(vt))(name);
395 					serializeImpl!(Serializer, Policy, typeof(vt), TA)(serializer, vt);
396 					serializer.endWriteDictionaryEntry!(typeof(vt))(name);
397 				} else {
398 					alias TA = TypeTuple!(); // FIXME: support attributes for tuples somehow
399 					enum name = underscoreStrip(mname);
400 					auto vt = tuple(__traits(getMember, value, mname));
401 					serializer.beginWriteDictionaryEntry!(typeof(vt))(name);
402 					serializeImpl!(Serializer, Policy, typeof(vt), TA)(serializer, vt);
403 					serializer.endWriteDictionaryEntry!(typeof(vt))(name);
404 				}
405 			}
406 			static if (__traits(compiles, serializer.endWriteDictionary!TU(0))) {
407 				serializer.endWriteDictionary!TU(nfields);
408 			} else {
409 				serializer.endWriteDictionary!TU();
410 			}
411 		}
412 	} else static if (isPointer!TU) {
413 		if (value is null) {
414 			serializer.writeValue(null);
415 			return;
416 		}
417 		serializeImpl!(Serializer, Policy, PointerTarget!TU)(serializer, *value);
418 	} else static if (is(TU == bool) || is(TU : real) || is(TU : long)) {
419 		serializeImpl!(Serializer, Policy, string)(serializer, to!string(value));
420 	} else static assert(false, "Unsupported serialization type: " ~ T.stringof);
421 }
422 
423 
424 private T deserializeImpl(T, alias Policy, Serializer, ATTRIBUTES...)(ref Serializer deserializer)
425 {
426 	import std.typecons : Nullable;
427 
428 	static assert(Serializer.isSupportedValueType!string, "All serializers must support string values.");
429 	static assert(Serializer.isSupportedValueType!(typeof(null)), "All serializers must support null values.");
430 
431 	static if (is(T == enum)) {
432 		static if (hasAttributeL!(ByNameAttribute, ATTRIBUTES)) {
433 			return deserializeImpl!(string, Policy, Serializer)(deserializer).to!T();
434 		} else {
435 			return cast(T)deserializeImpl!(OriginalType!T, Policy, Serializer)(deserializer);
436 		}
437 	} else static if (Serializer.isSupportedValueType!T) {
438 		return deserializer.readValue!T();
439 	} else static if (isStaticArray!T) {
440 		alias TV = typeof(T.init[0]);
441 		T ret;
442 		size_t i = 0;
443 		deserializer.readArray!T((sz) { assert(sz == 0 || sz == T.length); }, {
444 			assert(i < T.length);
445 			ret[i++] = deserializeImpl!(TV, Policy, Serializer, ATTRIBUTES)(deserializer);
446 		});
447 		return ret;
448 	} else static if (isDynamicArray!T) {
449 		alias TV = typeof(T.init[0]);
450 		//auto ret = appender!T();
451 		T ret; // Cannot use appender because of DMD BUG 10690/10859/11357
452 		deserializer.readArray!T((sz) { ret.reserve(sz); }, () {
453 			ret ~= deserializeImpl!(TV, Policy, Serializer, ATTRIBUTES)(deserializer);
454 		});
455 		return ret;//cast(T)ret.data;
456 	} else static if (isAssociativeArray!T) {
457 		alias TK = KeyType!T;
458 		alias TV = ValueType!T;
459 		T ret;
460 		deserializer.readDictionary!T((name) {
461 			TK key;
462 			static if (is(TK == string)) key = name;
463 			else static if (is(TK : real) || is(TK : long) || is(TK == enum)) key = name.to!TK;
464 			else static if (isStringSerializable!TK) key = TK.fromString(name);
465 			else static assert(false, "Associative array keys must be strings, numbers, enums, or have toString/fromString methods.");
466 			ret[key] = deserializeImpl!(TV, Policy, Serializer, ATTRIBUTES)(deserializer);
467 		});
468 		return ret;
469 	} else static if (isInstanceOf!(Nullable, T)) {
470 		if (deserializer.tryReadNull()) return T.init;
471 		return T(deserializeImpl!(typeof(T.init.get()), Policy, Serializer, ATTRIBUTES)(deserializer));
472 	} else static if (isPolicySerializable!(Policy, T)) {
473 		alias CustomType = typeof(Policy!T.toRepresentation(T.init));
474 		return Policy!T.fromRepresentation(deserializeImpl!(CustomType, Policy, Serializer, ATTRIBUTES)(deserializer));
475 	} else static if (isCustomSerializable!T) {
476 		alias CustomType = typeof(T.init.toRepresentation());
477 		return T.fromRepresentation(deserializeImpl!(CustomType, Policy, Serializer, ATTRIBUTES)(deserializer));
478 	} else static if (isISOExtStringSerializable!T) {
479 		return T.fromISOExtString(deserializer.readValue!string());
480 	} else static if (isStringSerializable!T) {
481 		return T.fromString(deserializer.readValue!string());
482 	} else static if (is(T == struct) || is(T == class)) {
483 		static if (is(T == class)) {
484 			if (deserializer.tryReadNull()) return null;
485 		}
486 
487 		bool[__traits(allMembers, T).length] set;
488 		string name;
489 		T ret;
490 		static if (is(T == class)) ret = new T;
491 
492 		static if (hasAttributeL!(AsArrayAttribute, ATTRIBUTES)) {
493 			size_t idx = 0;
494 			deserializer.readArray!T((sz){}, {
495 				static if (hasSerializableFields!T) {
496 					switch (idx++) {
497 						default: break;
498 						foreach (i, mname; SerializableFields!T) {
499 							alias TM = typeof(__traits(getMember, ret, mname));
500 							alias TA = TypeTuple!(__traits(getAttributes, __traits(getMember, ret, mname)));
501 							case i:
502 								static if (hasAttribute!(OptionalAttribute, __traits(getMember, T, mname)))
503 									if (deserializer.tryReadNull()) return;
504 								set[i] = true;
505 								__traits(getMember, ret, mname) = deserializeImpl!(TM, Serializer, TA)(deserializer);
506 								break;
507 						}
508 					}
509 				} else {
510 					pragma(msg, "Deserializing composite type "~T.stringof~" which has no serializable fields.");
511 				}
512 			});
513 		} else {
514 			deserializer.readDictionary!T((name) {
515 				static if (hasSerializableFields!T) {
516 					switch (name) {
517 						default: break;
518 						foreach (i, mname; SerializableFields!T) {
519 							alias TM = typeof(__traits(getMember, ret, mname));
520 							alias TA = TypeTuple!(__traits(getAttributes, __traits(getMember, ret, mname)));
521 							enum fname = getAttribute!(T, mname, NameAttribute)(NameAttribute(underscoreStrip(mname))).name;
522 							case fname:
523 								static if (hasAttribute!(OptionalAttribute, __traits(getMember, T, mname)))
524 									if (deserializer.tryReadNull()) return;
525 								set[i] = true;
526 								__traits(getMember, ret, mname) = deserializeImpl!(TM, Policy, Serializer, TA)(deserializer);
527 								break;
528 						}
529 					}
530 				} else {
531 					pragma(msg, "Deserializing composite type "~T.stringof~" which has no serializable fields.");
532 				}
533 			});
534 		}
535 		foreach (i, mname; SerializableFields!T)
536 			static if (!hasAttribute!(OptionalAttribute, __traits(getMember, T, mname)))
537 				enforce(set[i], "Missing non-optional field '"~mname~"' of type '"~T.stringof~"'.");
538 		return ret;
539 	} else static if (isPointer!T) {
540 		if (deserializer.isNull()) return null;
541 		alias PT = PointerTarget!T;
542 		auto ret = new PT;
543 		*ret = deserializeImpl!(PT, Policy)(deserializer);
544 		return ret;
545 	} else static if (is(T == bool) || is(T : real) || is(T : long)) {
546 		return to!T(deserializeImpl!(string, Policy)(deserializer));
547 	} else static assert(false, "Unsupported serialization type: " ~ T.stringof);
548 }
549 
550 
551 /**
552 	Attribute for overriding the field name during (de-)serialization.
553 */
554 NameAttribute name(string name)
555 {
556 	return NameAttribute(name);
557 }
558 ///
559 unittest {
560 	struct Test {
561 		@name("screen-size") int screenSize;
562 	}
563 }
564 
565 
566 /**
567 	Attribute marking a field as optional during deserialization.
568 */
569 @property OptionalAttribute optional()
570 {
571 	return OptionalAttribute();
572 }
573 ///
574 unittest {
575 	struct Test {
576 		// does not need to be present during deserialization
577 		@optional int screenSize = 100;
578 	}
579 }
580 
581 
582 /**
583 	Attribute for marking non-serialized fields.
584 */
585 @property IgnoreAttribute ignore()
586 {
587 	return IgnoreAttribute();
588 }
589 ///
590 unittest {
591 	struct Test {
592 		// is neither serialized not deserialized
593 		@ignore int screenSize;
594 	}
595 }
596 
597 
598 /**
599 	Attribute for forcing serialization of enum fields by name instead of by value.
600 */
601 @property ByNameAttribute byName()
602 {
603 	return ByNameAttribute();
604 }
605 ///
606 unittest {
607 	enum Color {
608 		red,
609 		green,
610 		blue
611 	}
612 
613 	struct Test {
614 		// serialized as an int (e.g. 1 for Color.green)
615 		Color color;
616 		// serialized as a string (e.g. "green" for Color.green)
617 		@byName Color namedColor;
618 		// serialized as array of ints
619 		Color[] colorArray;
620 		// serialized as array of strings
621 		@byName Color[] namedColorArray;
622 	}
623 }
624 
625 
626 /**
627 	Attribute for representing a struct/class as an array instead of an object.
628 
629 	Usually structs and class objects are serialized as dictionaries mapping
630 	from field name to value. Using this attribute, they will be serialized
631 	as a flat array instead. Note that changing the layout will make any
632 	already serialized data mismatch when this attribute is used.
633 */
634 @property AsArrayAttribute asArray()
635 {
636 	return AsArrayAttribute();
637 }
638 ///
639 unittest {
640 	struct Fields {
641 		int f1;
642 		string f2;
643 		double f3;
644 	}
645 
646 	struct Test {
647 		// serialized as name:value pairs ["f1": int, "f2": string, "f3": double]
648 		Fields object;
649 		// serialized as a sequential list of values [int, string, double]
650 		@asArray Fields array;
651 	}
652 
653 	import vson.json;
654 	static assert(is(typeof(serializeToJson(Test()))));
655 }
656 
657 
658 ///
659 enum FieldExistence
660 {
661 	missing,
662 	exists,
663 	defer
664 }
665 
666 /// User defined attribute (not intended for direct use)
667 struct NameAttribute { string name; }
668 /// ditto
669 struct OptionalAttribute {}
670 /// ditto
671 struct IgnoreAttribute {}
672 /// ditto
673 struct ByNameAttribute {}
674 /// ditto
675 struct AsArrayAttribute {}
676 
677 /**
678 	Checks if a given type has a custom serialization representation.
679 
680 	A class or struct type is custom serializable if it defines a pair of
681 	$(D toRepresentation)/$(D fromRepresentation) methods. Any class or
682 	struct type that has this trait will be serialized by using the return
683 	value of it's $(D toRepresentation) method instead of the original value.
684 
685 	This trait has precedence over $(D isISOExtStringSerializable) and
686 	$(D isStringSerializable).
687 */
688 template isCustomSerializable(T)
689 {
690 	enum bool isCustomSerializable = is(typeof(T.init.toRepresentation())) && is(typeof(T.fromRepresentation(T.init.toRepresentation())) == T);
691 }
692 ///
693 unittest {
694 	// represented as a single uint when serialized
695 	static struct S {
696 		ushort x, y;
697 
698 		uint toRepresentation() const { return x + (y << 16); }
699 		static S fromRepresentation(uint i) { return S(i & 0xFFFF, i >> 16); }
700 	}
701 
702 	static assert(isCustomSerializable!S);
703 }
704 
705 
706 /**
707 	Checks if a given type has an ISO extended string serialization representation.
708 
709 	A class or struct type is ISO extended string serializable if it defines a
710 	pair of $(D toISOExtString)/$(D fromISOExtString) methods. Any class or
711 	struct type that has this trait will be serialized by using the return
712 	value of it's $(D toISOExtString) method instead of the original value.
713 
714 	This is mainly useful for supporting serialization of the the date/time
715 	types in $(D std.datetime).
716 
717 	This trait has precedence over $(D isStringSerializable).
718 */
719 template isISOExtStringSerializable(T)
720 {
721 	enum bool isISOExtStringSerializable = is(typeof(T.init.toISOExtString()) == string) && is(typeof(T.fromISOExtString("")) == T);
722 }
723 ///
724 unittest {
725 	import std.datetime;
726 
727 	static assert(isISOExtStringSerializable!DateTime);
728 	static assert(isISOExtStringSerializable!SysTime);
729 
730 	// represented as an ISO extended string when serialized
731 	static struct S {
732 		// dummy example implementations
733 		string toISOExtString() const { return ""; }
734 		static S fromISOExtString(string s) { return S.init; }
735 	}
736 
737 	static assert(isISOExtStringSerializable!S);
738 }
739 
740 
741 /**
742 	Checks if a given type has a string serialization representation.
743 
744 	A class or struct type is string serializable if it defines a pair of
745 	$(D toString)/$(D fromString) methods. Any class or struct type that
746 	has this trait will be serialized by using the return value of it's
747 	$(D toString) method instead of the original value.
748 */
749 template isStringSerializable(T)
750 {
751 	enum bool isStringSerializable = is(typeof(T.init.toString()) == string) && is(typeof(T.fromString("")) == T);
752 }
753 ///
754 unittest {
755 	import std.conv;
756 
757 	// represented as the boxed value when serialized
758 	static struct Box(T) {
759 		T value;
760 	}
761 
762 	template BoxPol(S)
763 	{
764 		auto toRepresentation(S s) {
765 			return s.value;
766 		}
767 
768 		S fromRepresentation(typeof(S.init.value) v) {
769 			return S(v);
770 		}
771 	}
772 	static assert(isPolicySerializable!(BoxPol, Box!int));
773 }
774 
775 private template DefaultPolicy(T)
776 {
777 }
778 
779 /**
780 	Checks if a given policy supports custom serialization for a given type.
781 
782 	A class or struct type is custom serializable according to a policy if
783 	the policy defines a pair of $(D toRepresentation)/$(D fromRepresentation)
784 	functions. Any class or struct type that has this trait for the policy supplied to
785 	$D(serializeWithPolicy) will be serialized by using the return value of the
786 	policy $(D toRepresentation) function instead of the original value.
787 
788 	This trait has precedence over $(D isCustomSerializable),
789 	$(D isISOExtStringSerializable) and $(D isStringSerializable).
790 
791 	See_Also: vson.serialization.serializeWithPolicy
792 */
793 template isPolicySerializable(alias Policy, T)
794 {
795 	enum bool isPolicySerializable = is(typeof(Policy!T.toRepresentation(T.init))) && 
796 		is(typeof(Policy!T.fromRepresentation(Policy!T.toRepresentation(T.init))) == T);
797 }
798 ///
799 unittest {
800 	import std.conv;
801 	
802 	// represented as a string when serialized
803 	static struct S {
804 		int value;
805 		
806 		// dummy example implementations
807 		string toString() const { return value.to!string(); }
808 		static S fromString(string s) { return S(s.to!int()); }
809 	}
810 	
811 	static assert(isStringSerializable!S);
812 }
813 
814 /**
815 	Chains serialization policy.
816 
817 	Constructs a serialization policy that given a type $(D T) will apply the
818 	first compatible policy $(D toRepresentation) and $(D fromRepresentation)
819 	functions. Policies are evaluated left-to-right according to
820 	$(D isPolicySerializable).
821 
822 	See_Also: vson.serialization.serializeWithPolicy 
823 */
824 template ChainedPolicy(alias Primary, Fallbacks...)
825 {
826 	static if (Fallbacks.length == 0) {
827 		alias ChainedPolicy = Primary;
828 	} else {
829 		alias ChainedPolicy = ChainedPolicy!(ChainedPolicyImpl!(Primary, Fallbacks[0]), Fallbacks[1..$]);
830 	}
831 }
832 ///
833 unittest {
834 	import std.conv;
835 	
836 	// To be represented as the boxed value when serialized
837 	static struct Box(T) {
838 		T value;
839 	}
840 	// Also to berepresented as the boxed value when serialized, but has
841 	// a different way to access the value.
842 	static struct Box2(T) {
843 		private T v;
844 		ref T get() {
845 			return v;
846 		}
847 	}
848 	template BoxPol(S)
849 	{
850 		auto toRepresentation(S s) {
851 			return s.value;
852 		}
853 		
854 		S fromRepresentation(typeof(toRepresentation(S.init)) v) {
855 			return S(v);
856 		}
857 	}
858 	template Box2Pol(S)
859 	{
860 		auto toRepresentation(S s) {
861 			return s.get();
862 		}
863 		
864 		S fromRepresentation(typeof(toRepresentation(S.init)) v) {
865 			S s;
866 			s.get() = v;
867 			return s;
868 		}
869 	}
870 	alias ChainPol = ChainedPolicy!(BoxPol, Box2Pol);
871 	static assert(!isPolicySerializable!(BoxPol, Box2!int));
872 	static assert(!isPolicySerializable!(Box2Pol, Box!int));
873 	static assert(isPolicySerializable!(ChainPol, Box!int));
874 	static assert(isPolicySerializable!(ChainPol, Box2!int));
875 }
876 
877 private template ChainedPolicyImpl(alias Primary, alias Fallback)
878 {
879 	template Pol(T)
880 	{
881 		static if (isPolicySerializable!(Primary, T)) {
882 			alias toRepresentation = Primary!T.toRepresentation;
883 			alias fromRepresentation = Primary!T.fromRepresentation;
884 		} else {
885 			alias toRepresentation = Fallback!T.toRepresentation;
886 			alias fromRepresentation = Fallback!T.fromRepresentation;
887 		}
888 	}
889 	alias ChainedPolicyImpl = Pol;
890 }
891 
892 private template hasAttribute(T, alias decl) { enum hasAttribute = findFirstUDA!(T, decl).found; }
893 
894 unittest {
895 	@asArray int i1;
896 	static assert(hasAttribute!(AsArrayAttribute, i1));
897 	int i2;
898 	static assert(!hasAttribute!(AsArrayAttribute, i2));
899 }
900 
901 private template hasAttributeL(T, ATTRIBUTES...) {
902 	static if (ATTRIBUTES.length == 1) {
903 		enum hasAttributeL = is(typeof(ATTRIBUTES[0]) == T);
904 	} else static if (ATTRIBUTES.length > 1) {
905 		enum hasAttributeL = hasAttributeL!(T, ATTRIBUTES[0 .. $/2]) || hasAttributeL!(T, ATTRIBUTES[$/2 .. $]);
906 	} else {
907 		enum hasAttributeL = false;
908 	}
909 }
910 
911 unittest {
912 	static assert(hasAttributeL!(AsArrayAttribute, byName, asArray));
913 	static assert(!hasAttributeL!(AsArrayAttribute, byName));
914 }
915 
916 private static T getAttribute(TT, string mname, T)(T default_value)
917 {
918 	enum val = findFirstUDA!(T, __traits(getMember, TT, mname));
919 	static if (val.found) return val.value;
920 	else return default_value;
921 }
922 
923 private string underscoreStrip(string field_name)
924 {
925 	if( field_name.length < 1 || field_name[$-1] != '_' ) return field_name;
926 	else return field_name[0 .. $-1];
927 }
928 
929 
930 private template hasSerializableFields(T, size_t idx = 0)
931 {
932 	enum hasSerializableFields = SerializableFields!(T).length > 0;
933 	/*static if (idx < __traits(allMembers, T).length) {
934 		enum mname = __traits(allMembers, T)[idx];
935 		static if (!isRWPlainField!(T, mname) && !isRWField!(T, mname)) enum hasSerializableFields = hasSerializableFields!(T, idx+1);
936 		else static if (hasAttribute!(IgnoreAttribute, __traits(getMember, T, mname))) enum hasSerializableFields = hasSerializableFields!(T, idx+1);
937 		else enum hasSerializableFields = true;
938 	} else enum hasSerializableFields = false;*/
939 }
940 
941 private template SerializableFields(COMPOSITE)
942 {
943 	alias SerializableFields = FilterSerializableFields!(COMPOSITE, __traits(allMembers, COMPOSITE));
944 }
945 
946 private template FilterSerializableFields(COMPOSITE, FIELDS...)
947 {
948 	static if (FIELDS.length > 1) {
949 		alias FilterSerializableFields = TypeTuple!(
950 			FilterSerializableFields!(COMPOSITE, FIELDS[0 .. $/2]),
951 			FilterSerializableFields!(COMPOSITE, FIELDS[$/2 .. $]));
952 	} else static if (FIELDS.length == 1) {
953 		alias T = COMPOSITE;
954 		enum mname = FIELDS[0];
955 		static if (isRWPlainField!(T, mname) || isRWField!(T, mname)) {
956 			alias Tup = TypeTuple!(__traits(getMember, COMPOSITE, FIELDS[0]));
957 			static if (Tup.length != 1) {
958 				alias FilterSerializableFields = TypeTuple!(mname);
959 			} else {
960 				static if (!hasAttribute!(IgnoreAttribute, __traits(getMember, T, mname)))
961 					alias FilterSerializableFields = TypeTuple!(mname);
962 				else alias FilterSerializableFields = TypeTuple!();
963 			}
964 		} else alias FilterSerializableFields = TypeTuple!();
965 	} else alias FilterSerializableFields = TypeTuple!();
966 }
967 
968 private size_t getExpandedFieldCount(T, FIELDS...)()
969 {
970 	size_t ret = 0;
971 	foreach (F; FIELDS) ret += TypeTuple!(__traits(getMember, T, F)).length;
972 	return ret;
973 }
974 
975 /******************************************************************************/
976 /* General serialization unit testing                                         */
977 /******************************************************************************/
978 
979 version (unittest) {
980 	private struct TestSerializer {
981 		import std.array, std.conv, std.string;
982 
983 		string result;
984 
985 		enum isSupportedValueType(T) = is(T == string) || is(T == typeof(null)) || is(T == float) || is (T == int);
986 
987 		string getSerializedResult() { return result; }
988 		void beginWriteDictionary(T)() { result ~= "D("~T.stringof~"){"; }
989 		void endWriteDictionary(T)() { result ~= "}D("~T.stringof~")"; }
990 		void beginWriteDictionaryEntry(T)(string name) { result ~= "DE("~T.stringof~","~name~")("; }
991 		void endWriteDictionaryEntry(T)(string name) { result ~= ")DE("~T.stringof~","~name~")"; }
992 		void beginWriteArray(T)(size_t length) { result ~= "A("~T.stringof~")["~length.to!string~"]["; }
993 		void endWriteArray(T)() { result ~= "]A("~T.stringof~")"; }
994 		void beginWriteArrayEntry(T)(size_t i) { result ~= "AE("~T.stringof~","~i.to!string~")("; }
995 		void endWriteArrayEntry(T)(size_t i) { result ~= ")AE("~T.stringof~","~i.to!string~")"; }
996 		void writeValue(T)(T value) {
997 			if (is(T == typeof(null))) result ~= "null";
998 			else {
999 				assert(isSupportedValueType!T);
1000 				result ~= "V("~T.stringof~")("~value.to!string~")";
1001 			}
1002 		}
1003 
1004 		// deserialization
1005 		void readDictionary(T)(scope void delegate(string) entry_callback)
1006 		{
1007 			enum prefix = "D("~T.stringof~"){";
1008 			assert(result.startsWith(prefix));
1009 			result  = result[prefix.length .. $];
1010 			while (true) {
1011 				// ...
1012 				assert(false);
1013 			}
1014 		}
1015 
1016 		void readArray(T)(scope void delegate(size_t) size_callback, scope void delegate() entry_callback)
1017 		{
1018 			enum prefix = "A("~T.stringof~")[";
1019 			assert(result.startsWith(prefix));
1020 			result  = result[prefix.length .. $];
1021 			assert(false);
1022 		}
1023 
1024 		void readValue(T)()
1025 		{
1026 			assert(false);
1027 		}
1028 
1029 		bool tryReadNull()
1030 		{
1031 			if (result.startsWith("null")) {
1032 				result = result[4 .. $];
1033 				return true;
1034 			} else return false;
1035 		}
1036 	}
1037 }
1038 
1039 unittest { // basic serialization behavior
1040 	import std.typecons : Nullable;
1041 
1042 	assert(serialize!TestSerializer("hello") == "V(string)(hello)");
1043 	assert(serialize!TestSerializer(12) == "V(int)(12)");
1044 	assert(serialize!TestSerializer(12.0) == "V(string)(12)");
1045 	assert(serialize!TestSerializer(12.0f) == "V(float)(12)");
1046 	assert(serialize!TestSerializer(null) == "null");
1047 	assert(serialize!TestSerializer(["hello", "world"]) ==
1048 		"A(string[])[2][AE(string,0)(V(string)(hello))AE(string,0)AE(string,1)(V(string)(world))AE(string,1)]A(string[])");
1049 	assert(serialize!TestSerializer(["hello": "world"]) ==
1050 		"D(string[string]){DE(string,hello)(V(string)(world))DE(string,hello)}D(string[string])");
1051 	assert(serialize!TestSerializer(cast(int*)null) == "null");
1052 	int i = 42;
1053 	assert(serialize!TestSerializer(&i) == "V(int)(42)");
1054 	Nullable!int j;
1055 	assert(serialize!TestSerializer(j) == "null");
1056 	j = 42;
1057 	assert(serialize!TestSerializer(j) == "V(int)(42)");
1058 }
1059 
1060 unittest { // basic user defined types
1061 	static struct S { string f; }
1062 	auto s = S("hello");
1063 	assert(serialize!TestSerializer(s) == "D(S){DE(string,f)(V(string)(hello))DE(string,f)}D(S)");
1064 
1065 	static class C { string f; }
1066 	C c;
1067 	assert(serialize!TestSerializer(c) == "null");
1068 	c = new C;
1069 	c.f = "hello";
1070 	assert(serialize!TestSerializer(c) == "D(C){DE(string,f)(V(string)(hello))DE(string,f)}D(C)");
1071 
1072 	enum E { hello, world }
1073 	assert(serialize!TestSerializer(E.hello) == "V(int)(0)");
1074 	assert(serialize!TestSerializer(E.world) == "V(int)(1)");
1075 }
1076 
1077 unittest { // tuple serialization
1078 	static struct S(T...) { T f; }
1079 	auto s = S!(int, string)(42, "hello");
1080 	assert(serialize!TestSerializer(s) ==
1081 		"D(S!(int, string)){DE(Tuple!(int, string),f)(A(Tuple!(int, string))[2][AE(int,0)(V(int)(42))AE(int,0)AE(string,1)(V(string)(hello))AE(string,1)]A(Tuple!(int, string)))DE(Tuple!(int, string),f)}D(S!(int, string))");
1082 
1083 	static struct T { @asArray S!(int, string) g; }
1084 	auto t = T(s);
1085 	assert(serialize!TestSerializer(t) ==
1086 		"D(T){DE(S!(int, string),g)(A(S!(int, string))[2][AE(int,0)(V(int)(42))AE(int,0)AE(string,1)(V(string)(hello))AE(string,1)]A(S!(int, string)))DE(S!(int, string),g)}D(T)");
1087 }
1088 
1089 unittest { // testing the various UDAs
1090 	enum E { hello, world }
1091 	static struct S {
1092 		@byName E e;
1093 		@ignore int i;
1094 		@optional float f;
1095 	}
1096 	auto s = S(E.world, 42, 1.0f);
1097 	assert(serialize!TestSerializer(s) ==
1098 		"D(S){DE(E,e)(V(string)(world))DE(E,e)DE(float,f)(V(float)(1))DE(float,f)}D(S)");
1099 }
1100 
1101 unittest { // custom serialization support
1102 	// iso-ext
1103 	import std.datetime;
1104 	auto t = TimeOfDay(6, 31, 23);
1105 	assert(serialize!TestSerializer(t) == "V(string)(06:31:23)");
1106 	auto d = Date(1964, 1, 23);
1107 	assert(serialize!TestSerializer(d) == "V(string)(1964-01-23)");
1108 	auto dt = DateTime(d, t);
1109 	assert(serialize!TestSerializer(dt) == "V(string)(1964-01-23T06:31:23)");
1110 	auto st = SysTime(dt, UTC());
1111 	assert(serialize!TestSerializer(st) == "V(string)(1964-01-23T06:31:23Z)");
1112 
1113 	// string
1114 	struct S1 { int i; string toString() const { return "hello"; } static S1 fromString(string) { return S1.init; } }
1115 	struct S2 { int i; string toString() const { return "hello"; } }
1116 	struct S3 { int i; static S3 fromString(string) { return S3.init; } }
1117 	assert(serialize!TestSerializer(S1.init) == "V(string)(hello)");
1118 	assert(serialize!TestSerializer(S2.init) == "D(S2){DE(int,i)(V(int)(0))DE(int,i)}D(S2)");
1119 	assert(serialize!TestSerializer(S3.init) == "D(S3){DE(int,i)(V(int)(0))DE(int,i)}D(S3)");
1120 
1121 	// custom
1122 	struct C1 { int i; float toRepresentation() const { return 1.0f; } static C1 fromRepresentation(float f) { return C1.init; } }
1123 	struct C2 { int i; float toRepresentation() const { return 1.0f; } }
1124 	struct C3 { int i; static C3 fromRepresentation(float f) { return C3.init; } }
1125 	assert(serialize!TestSerializer(C1.init) == "V(float)(1)");
1126 	assert(serialize!TestSerializer(C2.init) == "D(C2){DE(int,i)(V(int)(0))DE(int,i)}D(C2)");
1127 	assert(serialize!TestSerializer(C3.init) == "D(C3){DE(int,i)(V(int)(0))DE(int,i)}D(C3)");
1128 }
1129 
1130 unittest // Testing corner case: member function returning by ref
1131 {
1132 	import vson.json;
1133 
1134 	static struct S
1135 	{
1136 		int i;
1137 		ref int foo() { return i; }
1138 	}
1139 
1140 	static assert(__traits(compiles, { S().serializeToJson(); }));
1141 	static assert(__traits(compiles, { Json().deserializeJson!S(); }));
1142 
1143 	auto s = S(1);
1144 	assert(s.serializeToJson().deserializeJson!S() == s);
1145 }
1146 
1147 unittest // Testing corner case: Variadic template constructors and methods
1148 {
1149 	import vson.json;
1150 
1151 	static struct S
1152 	{
1153 		int i;
1154 		this(Args...)(Args args) {}
1155 		int foo(Args...)(Args args) { return i; }
1156 		ref int bar(Args...)(Args args) { return i; }
1157 	}
1158 
1159 	static assert(__traits(compiles, { S().serializeToJson(); }));
1160 	static assert(__traits(compiles, { Json().deserializeJson!S(); }));
1161 
1162 	auto s = S(1);
1163 	assert(s.serializeToJson().deserializeJson!S() == s);
1164 }
1165 
1166 unittest // Make sure serializing through properties still works
1167 {
1168 	import vson.json;
1169 
1170 	static struct S
1171 	{
1172 		public int i;
1173 		private int privateJ;
1174 
1175 		@property int j() { return privateJ; }
1176 		@property void j(int j) { privateJ = j; }
1177 	}
1178 
1179 	auto s = S(1, 2);
1180 	assert(s.serializeToJson().deserializeJson!S() == s);
1181 }