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