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 `Typedef!T` will be serialized as if it were just `T`.)
27 			$(LI Any `BitFlags!T` value will be serialized as `T[]`)
28 			$(LI Types satisfying the `isPolicySerializable` trait for the
29 				supplied `Policy` will be serialized as the value returned
30 				by the policy `toRepresentation` function (again subject to
31 				these rules).)
32 			$(LI Types satisfying the `isCustomSerializable` trait will be
33 				serialized as the value returned by their `toRepresentation`
34 				method (again subject to these rules).)
35 			$(LI Types satisfying the `isISOExtStringSerializable` trait will be
36 				serialized as a string, as returned by their `toISOExtString`
37 				method. This causes types such as `SysTime` to be serialized
38 				as strings.)
39 			$(LI Types satisfying the `isStringSinkSerializable` trait will be
40 				serialized as a string using the `toString(sink)` method. `sink`
41 				can either be a delegate that takes a `char` array argument, or
42 				an output range of `char`.)
43 			$(LI Types satisfying the `isStringSerializable` trait will be
44 				serialized as a string, as returned by their `toString`
45 				method.)
46 			$(LI Struct and class types by default will be serialized as
47 				associative arrays, where the key is the name of the
48 				corresponding field (can be overridden using the `@name`
49 				attribute). If the struct/class is annotated with `@asArray`,
50 				it will instead be serialized as a flat array of values in the
51 				order of declaration. Null class references will be serialized
52 				as `null`.)
53 			$(LI Pointer types will be serialized as either `null`, or as
54 				the value they point to.)
55 			$(LI Built-in integers and floating point values, as well as
56 				boolean values will be converted to strings, if the serializer
57 				doesn't support them directly.)
58 		)
59 
60 		Note that no aliasing detection is performed, so that pointers, class
61 		references and arrays referencing the same memory will be serialized
62 		as multiple copies. When in turn deserializing the data, they will also
63 		end up as separate copies in memory.
64 
65 	Field_names:
66 		By default, the field name of the serialized D type (for `struct` and
67 		`class` aggregates) is represented as-is in the serialized result. To
68 		circumvent name clashes with D's keywords, a single trailing underscore of
69 		any field name is stipped, so that a field name of `version_` results in
70 		just `"version"` as the serialized value. Names can also be freely
71 		customized using the `@name` annotation.
72 
73 		Associative array keys are always represented using their direct string
74 		representation.
75 
76 	Serializer_implementation:
77 		Serializers are implemented in terms of a struct with template methods that
78 		get called by the serialization framework:
79 
80 		---
81 		struct ExampleSerializer {
82 			enum isSupportedValueType(T) = is(T == string) || is(T == typeof(null));
83 
84 			// serialization
85 			auto getSerializedResult();
86 			void beginWriteDocument(TypeTraits)();
87 			void endWriteDocument(TypeTraits)();
88 			void beginWriteDictionary(TypeTraits)(size_t length); [OR] void beginWriteDictionary(TypeTraits)();
89 			void endWriteDictionary(TypeTraits)();
90 			void beginWriteDictionaryEntry(ElementTypeTraits)(string name);
91 			void endWriteDictionaryEntry(ElementTypeTraits)(string name);
92 			void beginWriteArray(TypeTraits)(size_t length);
93 			void endWriteArray(TypeTraits)();
94 			void beginWriteArrayEntry(ElementTypeTraits)(size_t index);
95 			void endWriteArrayEntry(ElementTypeTraits)(size_t index);
96 			void writeValue(TypeTraits, T)(T value);
97 
98 			// deserialization
99 
100 			void readDictionary(TypeTraits)(scope void delegate(string) entry_callback);
101 			void beginReadDictionaryEntry(ElementTypeTraits)(string);
102 			void endReadDictionaryEntry(ElementTypeTraits)(string);
103 			void readArray(TypeTraits)(scope void delegate(size_t) size_callback, scope void delegate() entry_callback);
104 			void beginReadArrayEntry(ElementTypeTraits)(size_t index);
105 			void endReadArrayEntry(ElementTypeTraits)(size_t index);
106 			T readValue(TypeTraits, T)();
107 			bool tryReadNull(TypeTraits)();
108 
109 			// skipValue() is optional. It will be called by the entry_callback in readDictionary
110 			// whenever the key passed to the entry_callback cannot be found.
111 			void skipValue();
112 		}
113 		---
114 
115 		The `TypeTraits` type passed to the individual methods has the following members:
116 		$(UL
117 			$(LI `Type`: The original type of the field to serialize)
118 			$(LI `Attributes`: User defined attributes attached to the field)
119 			$(LI `Policy`: An alias to the policy used for the serialization process)
120 		)
121 
122 		`ElementTypeTraits` have the following additional members:
123 		$(UL
124 			$(LI `ContainerType`: The original type of the enclosing container type)
125 			$(LI `ContainerAttributes`: User defined attributes attached to the enclosing container)
126 		)
127 
128 	Copyright: © 2013-2016 rejectedsoftware e.K.
129 	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
130 	Authors: Sönke Ludwig
131 */
132 module dub.internal.vibecompat.data.serialization;
133 
134 version (Have_vibe_serialization)
135     public import vibe.data.serialization;
136 else:
137 
138 import dub.internal.vibecompat.data.traits;
139 import dub.internal.vibecompat.data.uda;
140 
141 import std.array : Appender, appender;
142 import std.conv : ConvException, to;
143 import std.exception : enforce;
144 import std.range.primitives : ElementType, isInputRange;
145 import std.traits;
146 import std.meta;
147 
148 
149 /**
150 	Serializes a value with the given serializer.
151 
152 	The serializer must have a value result for the first form
153 	to work. Otherwise, use the range based form.
154 
155 	See_Also: `vibe.data.json.JsonSerializer`, `vibe.data.json.JsonStringSerializer`, `vibe.data.bson.BsonSerializer`
156 */
157 auto serialize(Serializer, T, ARGS...)(auto ref T value, ARGS args)
158 {
159 	auto serializer = Serializer(args);
160 	serialize(serializer, value);
161 	return serializer.getSerializedResult();
162 }
163 /// ditto
164 void serialize(Serializer, T)(ref Serializer serializer, auto ref T value)
165 {
166 	serializeWithPolicy!(Serializer, DefaultPolicy)(serializer, value);
167 }
168 
169 /** Note that there is a convenience function `vibe.data.json.serializeToJson`
170 	that can be used instead of manually invoking `serialize`.
171 */
172 unittest {
173 	import dub.internal.vibecompat.data.json;
174 
175 	struct Test {
176 		int value;
177 		string text;
178 	}
179 
180 	Test test;
181 	test.value = 12;
182 	test.text = "Hello";
183 
184 	Json serialized = serialize!JsonSerializer(test);
185 	assert(serialized["value"].get!int == 12);
186 	assert(serialized["text"].get!string == "Hello");
187 }
188 
189 unittest {
190 	import dub.internal.vibecompat.data.json;
191 
192 	// Make sure that immutable(char[]) works just like string
193 	// (i.e., immutable(char)[]).
194 	immutable key = "answer";
195 	auto ints = [key: 42];
196 	auto serialized = serialize!JsonSerializer(ints);
197 	assert(serialized[key].get!int == 42);
198 }
199 
200 /**
201 	Serializes a value with the given serializer, representing values according to `Policy` when possible.
202 
203 	The serializer must have a value result for the first form
204 	to work. Otherwise, use the range based form.
205 
206 	See_Also: `vibe.data.json.JsonSerializer`, `vibe.data.json.JsonStringSerializer`, `vibe.data.bson.BsonSerializer`
207 */
208 auto serializeWithPolicy(Serializer, alias Policy, T, ARGS...)(auto ref T value, ARGS args)
209 {
210 	auto serializer = Serializer(args);
211 	serializeWithPolicy!(Serializer, Policy)(serializer, value);
212 	return serializer.getSerializedResult();
213 }
214 /// ditto
215 void serializeWithPolicy(Serializer, alias Policy, T)(ref Serializer serializer, auto ref T value)
216 {
217 	static if (is(typeof(serializer.beginWriteDocument!T())))
218 		serializer.beginWriteDocument!T();
219 	serializeValueImpl!(Serializer, Policy).serializeValue!T(serializer, value);
220 	static if (is(typeof(serializer.endWriteDocument!T())))
221 		serializer.endWriteDocument!T();
222 }
223 ///
224 version (unittest)
225 {
226 }
227 
228 ///
229 unittest {
230 	import dub.internal.vibecompat.data.json;
231 	import std.meta : AliasSeq;
232 
233 	template SizePol(T)
234 		if (__traits(allMembers, T) == AliasSeq!("x", "y"))
235 	{
236 		import std.conv;
237 		import std.array;
238 
239 		static string toRepresentation(T value) @safe {
240 			return to!string(value.x) ~ "x" ~ to!string(value.y);
241 		}
242 
243 		static T fromRepresentation(string value) {
244 			string[] fields = value.split('x');
245 			alias fieldT = typeof(T.x);
246 			auto x = to!fieldT(fields[0]);
247 			auto y = to!fieldT(fields[1]);
248 			return T(x, y);
249 		}
250 	}
251 
252 	static struct SizeI {
253 		int x;
254 		int y;
255 	}
256 	SizeI sizeI = SizeI(1,2);
257 	Json serializedI = serializeWithPolicy!(JsonSerializer, SizePol)(sizeI);
258 	assert(serializedI.get!string == "1x2");
259 
260 	static struct SizeF {
261 		float x;
262 		float y;
263 	}
264 	SizeF sizeF = SizeF(0.1f,0.2f);
265 	Json serializedF = serializeWithPolicy!(JsonSerializer, SizePol)(sizeF);
266 	assert(serializedF.get!string == "0.1x0.2");
267 }
268 
269 
270 /**
271 	Deserializes and returns a serialized value.
272 
273 	serialized_data can be either an input range or a value containing
274 	the serialized data, depending on the type of serializer used.
275 
276 	See_Also: `vibe.data.json.JsonSerializer`, `vibe.data.json.JsonStringSerializer`, `vibe.data.bson.BsonSerializer`
277 */
278 T deserialize(Serializer, T, ARGS...)(ARGS args)
279 {
280 	return deserializeWithPolicy!(Serializer, DefaultPolicy, T)(args);
281 }
282 
283 /** Note that there is a convenience function `vibe.data.json.deserializeJson`
284 	that can be used instead of manually invoking `deserialize`.
285 */
286 unittest {
287 	import dub.internal.vibecompat.data.json;
288 
289 	struct Test {
290 		int value;
291 		string text;
292 	}
293 
294 	Json serialized = Json.emptyObject;
295 	serialized["value"] = 12;
296 	serialized["text"] = "Hello";
297 
298 	Test test = deserialize!(JsonSerializer, Test)(serialized);
299 	assert(test.value == 12);
300 	assert(test.text == "Hello");
301 }
302 
303 /**
304 	Deserializes and returns a serialized value, interpreting values according to `Policy` when possible.
305 
306 	serialized_data can be either an input range or a value containing
307 	the serialized data, depending on the type of serializer used.
308 
309 	See_Also: `vibe.data.json.JsonSerializer`, `vibe.data.json.JsonStringSerializer`, `vibe.data.bson.BsonSerializer`
310 */
311 T deserializeWithPolicy(Serializer, alias Policy, T, ARGS...)(ARGS args)
312 {
313 	auto deserializer = Serializer(args);
314 	return deserializeValueImpl!(Serializer, Policy).deserializeValue!T(deserializer);
315 }
316 
317 ///
318 unittest {
319 	import dub.internal.vibecompat.data.json;
320 	import std.meta : AliasSeq;
321 
322 	template SizePol(T)
323 		if (__traits(allMembers, T) == AliasSeq!("x", "y"))
324 	{
325 		import std.conv;
326 		import std.array;
327 
328 		static string toRepresentation(T value)
329 		@safe {
330 			return to!string(value.x) ~ "x" ~ to!string(value.y);
331 		}
332 
333 		static T fromRepresentation(string value)
334 		@safe {
335 			string[] fields = value.split('x');
336 			alias fieldT = typeof(T.x);
337 			auto x = to!fieldT(fields[0]);
338 			auto y = to!fieldT(fields[1]);
339 			return T(x, y);
340 		}
341 	}
342 
343 	static struct SizeI {
344 		int x;
345 		int y;
346 	}
347 
348 	Json serializedI = "1x2";
349 	SizeI sizeI = deserializeWithPolicy!(JsonSerializer, SizePol, SizeI)(serializedI);
350 	assert(sizeI.x == 1);
351 	assert(sizeI.y == 2);
352 
353 	static struct SizeF {
354 		float x;
355 		float y;
356 	}
357 	Json serializedF = "0.1x0.2";
358 	SizeF sizeF = deserializeWithPolicy!(JsonSerializer, SizePol, SizeF)(serializedF);
359 	assert(sizeF.x == 0.1f);
360 	assert(sizeF.y == 0.2f);
361 }
362 
363 private template serializeValueImpl(Serializer, alias Policy) {
364 	alias _Policy = Policy;
365 	static assert(Serializer.isSupportedValueType!string, "All serializers must support string values.");
366 	static assert(Serializer.isSupportedValueType!(typeof(null)), "All serializers must support null values.");
367 
368 	// work around https://issues.dlang.org/show_bug.cgi?id=16528
369 	static if (isSafeSerializer!Serializer) {
370 		void serializeValue(T, ATTRIBUTES...)(ref Serializer ser, auto ref T value) @safe { serializeValueDeduced!(T, ATTRIBUTES)(ser, value); }
371 	} else {
372 		void serializeValue(T, ATTRIBUTES...)(ref Serializer ser, auto ref T value) { serializeValueDeduced!(T, ATTRIBUTES)(ser, value); }
373 	}
374 
375 	private void serializeValueDeduced(T, FIELD_ATTRIBUTES...)(ref Serializer ser, auto ref T value)
376 	{
377 		import std.typecons : BitFlags, Nullable, Tuple, Typedef, TypedefType, tuple;
378 
379 		alias TU = Unqual!T;
380 
381 		alias ATTRIBUTES = AliasSeq!(FIELD_ATTRIBUTES, TypeAttributes!T);
382 
383 		alias Traits = .Traits!(TU, _Policy, ATTRIBUTES);
384 
385 		static if (isPolicySerializable!(Policy, TU)) {
386 			alias CustomType = typeof(Policy!TU.toRepresentation(TU.init));
387 			ser.serializeValue!(CustomType, ATTRIBUTES)(Policy!TU.toRepresentation(value));
388 		} else static if (is(TU == enum)) {
389 			static if (hasPolicyAttributeL!(ByNameAttribute, Policy, ATTRIBUTES)) {
390 				ser.serializeValue!(string)(value.to!string());
391 			} else {
392 				ser.serializeValue!(OriginalType!TU)(cast(OriginalType!TU)value);
393 			}
394 		} else static if (Serializer.isSupportedValueType!TU) {
395 			static if (is(TU == typeof(null))) ser.writeValue!Traits(null);
396 			else ser.writeValue!(Traits)(value);
397 		} else static if (/*isInstanceOf!(Tuple, TU)*/is(T == Tuple!TPS, TPS...)) {
398 			import std.algorithm.searching: all;
399 			static if (all!"!a.empty"([TU.fieldNames]) &&
400 					   !hasPolicyAttributeL!(AsArrayAttribute, Policy, ATTRIBUTES)) {
401 				static if (__traits(compiles, ser.beginWriteDictionary!TU(0))) {
402 					auto nfields = value.length;
403 					ser.beginWriteDictionary!Traits(nfields);
404 				} else {
405 					ser.beginWriteDictionary!Traits();
406 				}
407 				foreach (i, _; T.Types) {
408 					alias TV = typeof(value[i]);
409 					alias STraits = SubTraits!(Traits, TV);
410 					ser.beginWriteDictionaryEntry!STraits(underscoreStrip(TU.fieldNames[i]));
411 					ser.serializeValue!(TV, ATTRIBUTES)(value[i]);
412 					ser.endWriteDictionaryEntry!STraits(underscoreStrip(TU.fieldNames[i]));
413 				}
414 				static if (__traits(compiles, ser.endWriteDictionary!TU(0))) {
415 					ser.endWriteDictionary!Traits(nfields);
416 				} else {
417 					ser.endWriteDictionary!Traits();
418 				}
419 			} else static if (TU.Types.length == 1) {
420 				ser.serializeValue!(typeof(value[0]), ATTRIBUTES)(value[0]);
421 			} else {
422 				ser.beginWriteArray!Traits(value.length);
423 				foreach (i, _; T.Types) {
424 					alias TV = typeof(value[i]);
425 					alias STraits = SubTraits!(Traits, TV);
426 					ser.beginWriteArrayEntry!STraits(i);
427 					ser.serializeValue!(TV, ATTRIBUTES)(value[i]);
428 					ser.endWriteArrayEntry!STraits(i);
429 				}
430 				ser.endWriteArray!Traits();
431 			}
432 		} else static if (isArray!TU) {
433 			alias TV = typeof(value[0]);
434 			alias STraits = SubTraits!(Traits, TV);
435 			ser.beginWriteArray!Traits(value.length);
436 			foreach (i, ref el; value) {
437 				ser.beginWriteArrayEntry!STraits(i);
438 				ser.serializeValue!(TV, ATTRIBUTES)(el);
439 				ser.endWriteArrayEntry!STraits(i);
440 			}
441 			ser.endWriteArray!Traits();
442 		} else static if (isAssociativeArray!TU) {
443 			alias TK = KeyType!TU;
444 			alias TV = ValueType!TU;
445 			alias STraits = SubTraits!(Traits, TV);
446 
447 			static if (__traits(compiles, ser.beginWriteDictionary!TU(0))) {
448 				auto nfields = value.length;
449 				ser.beginWriteDictionary!Traits(nfields);
450 			} else {
451 				ser.beginWriteDictionary!Traits();
452 			}
453 			foreach (key, ref el; value) {
454 				string keyname;
455 				static if (is(TK : string)) keyname = key;
456 				else static if (is(TK : real) || is(TK : long) || is(TK == enum)) keyname = key.to!string;
457 				else static if (isStringSerializable!TK) keyname = key.toString();
458 				else static assert(false, "Associative array keys must be strings, numbers, enums, or have toString/fromString methods.");
459 				ser.beginWriteDictionaryEntry!STraits(keyname);
460 				ser.serializeValue!(TV, ATTRIBUTES)(el);
461 				ser.endWriteDictionaryEntry!STraits(keyname);
462 			}
463 			static if (__traits(compiles, ser.endWriteDictionary!TU(0))) {
464 				ser.endWriteDictionary!Traits(nfields);
465 			} else {
466 				ser.endWriteDictionary!Traits();
467 			}
468 		} else static if (/*isInstanceOf!(Nullable, TU)*/is(T == Nullable!TPS, TPS...)) {
469 			if (value.isNull()) ser.serializeValue!(typeof(null))(null);
470 			else ser.serializeValue!(typeof(value.get()), ATTRIBUTES)(value.get());
471 		} else static if (isInstanceOf!(Typedef, TU)) {
472 			ser.serializeValue!(TypedefType!TU, ATTRIBUTES)(cast(TypedefType!TU)value);
473 		} else static if (is(TU == BitFlags!E, E)) {
474 			alias STraits = SubTraits!(Traits, E);
475 
476 			size_t cnt = 0;
477 			foreach (v; EnumMembers!E)
478 				if (value & v)
479 					cnt++;
480 
481 			ser.beginWriteArray!Traits(cnt);
482 			cnt = 0;
483 			foreach (v; EnumMembers!E)
484 				if (value & v) {
485 					ser.beginWriteArrayEntry!STraits(cnt);
486 					ser.serializeValue!(E, ATTRIBUTES)(v);
487 					ser.endWriteArrayEntry!STraits(cnt);
488 					cnt++;
489 				}
490 			ser.endWriteArray!Traits();
491 		} else static if (isCustomSerializable!TU) {
492 			alias CustomType = typeof(T.init.toRepresentation());
493 			ser.serializeValue!(CustomType, ATTRIBUTES)(value.toRepresentation());
494 		} else static if (isISOExtStringSerializable!TU) {
495 			ser.serializeValue!(string, ATTRIBUTES)(value.toISOExtString());
496 		} else static if (isStringSinkSerializable!TU) {
497 			static if (doesSerializerSupportStringSink!Serializer) {
498 				ser.writeStringSinkValue!Traits(value);
499 			} else {
500 				import std.format : formattedWrite;
501 				auto app = appender!string;
502 				app.formattedWrite("%s", value);
503 				ser.serializeValue!(string, ATTRIBUTES)(app.data);
504 			}
505 		} else static if (isStringSerializable!TU) {
506 			ser.serializeValue!(string, ATTRIBUTES)(value.toString());
507 		} else static if (is(TU == struct) || is(TU == class)) {
508 			static if (!hasSerializableFields!(TU, Policy))
509 				pragma(msg, "Serializing composite type "~T.stringof~" which has no serializable fields");
510 			static if (is(TU == class)) {
511 				if (value is null) {
512 					ser.serializeValue!(typeof(null))(null);
513 					return;
514 				}
515 			}
516 			static auto safeGetMember(string mname)(ref T val) @safe {
517 				static if (__traits(compiles, __traits(getMember, val, mname))) {
518 					return __traits(getMember, val, mname);
519 				} else {
520 					pragma(msg, "Warning: Getter for "~fullyQualifiedName!T~"."~mname~" is not @safe");
521 					return () @trusted { return __traits(getMember, val, mname); } ();
522 				}
523 			}
524 			static if (hasPolicyAttributeL!(AsArrayAttribute, Policy, ATTRIBUTES)) {
525 				enum nfields = getExpandedFieldCount!(TU, SerializableFields!(TU, Policy));
526 				ser.beginWriteArray!Traits(nfields);
527 				size_t fcount = 0;
528 				foreach (mname; SerializableFields!(TU, Policy)) {
529 					alias TMS = AliasSeq!(typeof(__traits(getMember, value, mname)));
530 					foreach (j, TM; TMS) {
531 						alias TA = AliasSeq!(__traits(getAttributes, AliasSeq!(__traits(getMember, T, mname))[j]));
532 						alias STraits = SubTraits!(Traits, TM, TA);
533 						ser.beginWriteArrayEntry!STraits(fcount);
534 						static if (!isBuiltinTuple!(T, mname))
535 							ser.serializeValue!(TM, TA)(safeGetMember!mname(value));
536 						else
537 							ser.serializeValue!(TM, TA)(tuple(__traits(getMember, value, mname))[j]);
538 						ser.endWriteArrayEntry!STraits(fcount);
539 						fcount++;
540 					}
541 				}
542 				ser.endWriteArray!Traits();
543 			} else {
544 				static if (__traits(compiles, ser.beginWriteDictionary!Traits(0))) {
545 					auto nfields = getExpandedFieldCount!(TU, SerializableFields!(TU, Policy));
546 
547 					foreach (mname; SerializableFields!(TU, Policy)) {
548 						static if (!isBuiltinTuple!(T, mname)) {
549 							auto vt = safeGetMember!mname(value);
550 							static if (is(typeof(vt) : Nullable!NVT, NVT)
551 									&& hasPolicyAttribute!(EmbedNullableIgnoreNullAttribute, Policy, AliasSeq!(__traits(getMember, T, mname))[0])) {
552 								if (vt.isNull) nfields--;
553 							}
554 						}
555 					}
556 
557 					ser.beginWriteDictionary!Traits(nfields);
558 				} else {
559 					ser.beginWriteDictionary!Traits();
560 				}
561 				foreach (mname; SerializableFields!(TU, Policy)) {
562 					alias TM = AliasSeq!(typeof(__traits(getMember, TU, mname)));
563 					static if (__traits(getOverloads, T, mname).length > 0) {
564 						// combine attributes of overload sets
565 						alias getAtts(alias ovl) = __traits(getAttributes, ovl);
566 						alias TA = staticMap!(getAtts, __traits(getOverloads, T, mname));
567 					} else {
568 						alias TA = AliasSeq!(__traits(getAttributes, AliasSeq!(__traits(getMember, T, mname))[0]));
569 					}
570 					enum name = getPolicyAttribute!(fullyQualifiedName!TU~"."~mname, NameAttribute, Policy, TA)(NameAttribute!DefaultPolicy(underscoreStrip(mname))).name;
571 					static if (!isBuiltinTuple!(T, mname)) {
572 						auto vtn = safeGetMember!mname(value);
573 						static if (is(typeof(vtn) : Nullable!NVT, NVT)
574 								&& hasPolicyAttribute!(EmbedNullableIgnoreNullAttribute, Policy, AliasSeq!(__traits(getMember, T, mname))[0])) {
575 							if (vtn.isNull) continue;
576 							auto vt = vtn.get;
577 						} else {
578 							auto vt = vtn;
579 						}
580 					} else {
581 						alias TTM = AliasSeq!(typeof(__traits(getMember, value, mname)));
582 						auto vt = tuple!TTM(__traits(getMember, value, mname));
583 					}
584 					alias STraits = SubTraits!(Traits, typeof(vt), TA);
585 					ser.beginWriteDictionaryEntry!STraits(name);
586 					ser.serializeValue!(typeof(vt), TA)(vt);
587 					ser.endWriteDictionaryEntry!STraits(name);
588 				}
589 				static if (__traits(compiles, ser.endWriteDictionary!Traits(0))) {
590 					ser.endWriteDictionary!Traits(nfields);
591 				} else {
592 					ser.endWriteDictionary!Traits();
593 				}
594 			}
595 		} else static if (isPointer!TU) {
596 			if (value is null) {
597 				ser.writeValue!Traits(null);
598 				return;
599 			}
600 			ser.serializeValue!(PointerTarget!TU)(*value);
601 		} else static if (is(TU == bool) || is(TU : real) || is(TU : long)) {
602 			ser.serializeValue!(string, ATTRIBUTES)(to!string(value));
603 		} else static assert(false, "Unsupported serialization type: " ~ T.stringof);
604 	}
605 }
606 
607 ///
608 package template doesSerializerSupportStringSink(SerT)
609 {
610 	static struct T1 { void toString(scope void delegate(scope const(char)[])) {} }
611 	static struct T2 { void toString(R)(ref R dst) { dst.put('f'); dst.put("foo"); } }
612 
613 	enum doesSerializerSupportStringSink =
614 		is(typeof(SerT.init.writeStringSinkValue!(Traits!(T1, DefaultPolicy))(T1.init)))
615 		&& is(typeof(SerT.init.writeStringSinkValue!(Traits!(T2, DefaultPolicy))(T2.init)));
616 }
617 
618 ///
619 template isStringSinkSerializable(T)
620 {
621 	import std.range : nullSink;
622 
623 	private void sink(S : const(char)[])(scope S s) @safe {}
624 
625 	enum isStringSinkSerializable =
626 		(
627 			is(typeof(T.init.toString((scope str) => sink(str))))
628 			|| is(typeof(T.init.toString(nullSink)))
629 		)
630 		&& is(typeof(T.fromString(string.init)) : T);
631 }
632 
633 unittest {
634 	import std.array : split;
635 	import std.format : formattedWrite;
636 	import dub.internal.vibecompat.data.json;
637 
638 	static struct X(alias hasSink) {
639 		private int i;
640 		private string s;
641 
642 		static if (hasSink) {
643 			void toString (scope void delegate(scope const(char)[]) @safe dg) @safe
644 			{
645 				formattedWrite(dg, "%d;%s", this.i, this.s);
646 			}
647 		}
648 
649 		string toString () @safe const pure nothrow
650 		{
651 			return "42;hello";
652 		}
653 
654 		static X fromString (string s) @safe pure
655 		{
656 			auto parts = s.split(";");
657 			auto x = X(parts[0].to!int, parts[1]);
658 			return x;
659 		}
660 	}
661 
662 	static assert(!isStringSinkSerializable!(X!false));
663 	static assert(isStringSinkSerializable!(X!true));
664 
665 	// old toString() style methods still work if no sink overload presented
666 	auto serialized1 = X!false(7,"x1").serializeToJsonString();
667 	assert(serialized1 == `"42;hello"`);
668 	auto deserialized1 = deserializeJson!(X!false)(serialized1);
669 	assert(deserialized1.i == 42);
670 	assert(deserialized1.s == "hello");
671 
672 	// sink overload takes precedence
673 	auto serialized2 = X!true(7,"x2").serializeToJsonString();
674 	assert(serialized2 == `"7;x2"`);
675 	auto deserialized2 = deserializeJson!(X!true)(serialized2);
676 	assert(deserialized2.i == 7);
677 	assert(deserialized2.s == "x2");
678 
679 	// type is sink serializable, but serializer doesn't support sink
680 	auto serialized3 = X!true(7,"x2").serializeToJson();
681 	assert(to!string(serialized3) == `"7;x2"`);
682 	auto deserialized3 = deserializeJson!(X!true)(serialized3);
683 	assert(deserialized3.i == 7);
684 	assert(deserialized3.s == "x2");
685 }
686 
687 private struct Traits(T, alias POL, ATTRIBUTES...)
688 {
689 	alias Type = T;
690 	alias Policy = POL;
691 	alias Attributes = AliasSeq!ATTRIBUTES;
692 }
693 
694 private struct SubTraits(Traits, T, A...)
695 {
696 	alias Type = Unqual!T;
697 	alias Attributes = AliasSeq!A;
698 	alias Policy = Traits.Policy;
699 	alias ContainerType = Traits.Type;
700 	alias ContainerAttributes = Traits.Attributes;
701 }
702 
703 private template deserializeValueImpl(Serializer, alias Policy) {
704 	alias _Policy = Policy;
705 	static assert(Serializer.isSupportedValueType!string, "All serializers must support string values.");
706 	static assert(Serializer.isSupportedValueType!(typeof(null)), "All serializers must support null values.");
707 
708 	// work around https://issues.dlang.org/show_bug.cgi?id=16528
709 	static if (isSafeDeserializer!Serializer) {
710 		T deserializeValue(T, ATTRIBUTES...)(ref Serializer ser) @safe { return deserializeValueDeduced!(T, ATTRIBUTES)(ser); }
711 	} else {
712 		T deserializeValue(T, ATTRIBUTES...)(ref Serializer ser) { return deserializeValueDeduced!(T, ATTRIBUTES)(ser); }
713 	}
714 
715 	T deserializeValueDeduced(T, ATTRIBUTES...)(ref Serializer ser) if(!isMutable!T)
716 	{
717 		import std.algorithm.mutation : move;
718 		auto ret = deserializeValue!(Unqual!T, ATTRIBUTES)(ser);
719 		return () @trusted { return cast(T)ret.move; } ();
720 	}
721 
722 	T deserializeValueDeduced(T, FIELD_ATTRIBUTES...)(ref Serializer ser) if(isMutable!T)
723 	{
724 		import std.typecons : BitFlags, Nullable, Typedef, TypedefType, Tuple;
725 
726 		alias ATTRIBUTES = AliasSeq!(FIELD_ATTRIBUTES, TypeAttributes!T);
727 
728 		alias Traits = .Traits!(T, _Policy, ATTRIBUTES);
729 
730 		static if (isPolicySerializable!(Policy, T)) {
731 			alias CustomType = typeof(Policy!T.toRepresentation(T.init));
732 			return Policy!T.fromRepresentation(ser.deserializeValue!(CustomType, ATTRIBUTES));
733 		} else static if (is(T == enum)) {
734 			static if (hasPolicyAttributeL!(ByNameAttribute, Policy, ATTRIBUTES)) {
735 				return ser.deserializeValue!(string, ATTRIBUTES).to!T();
736 			} else static if (isSomeString!(OriginalType!T)) {
737 				auto value = ser.deserializeValue!(OriginalType!T);
738 				switch (value) {
739 					default:
740 						throw new ConvException("Unexpected enum value " ~ value.to!string);
741 					static foreach (enumvalue; NoDuplicates!(EnumMembers!T))
742 						case enumvalue: return enumvalue;
743 				}
744 			} else {
745 				return cast(T)ser.deserializeValue!(OriginalType!T);
746 			}
747 		} else static if (Serializer.isSupportedValueType!T) {
748 			return ser.readValue!(Traits, T)();
749 		} else static if (/*isInstanceOf!(Tuple, TU)*/is(T == Tuple!TPS, TPS...)) {
750 			enum fieldsCount = T.Types.length;
751 			import std.algorithm.searching: all;
752 			static if (all!"!a.empty"([T.fieldNames]) &&
753 					   !hasPolicyAttributeL!(AsArrayAttribute, Policy, ATTRIBUTES)) {
754 				T ret;
755 				bool[fieldsCount] set;
756 				ser.readDictionary!Traits((name) {
757 					switch (name) {
758 						default:
759 							static if (is(typeof(ser.skipValue()))) {
760 								ser.skipValue();
761 							}
762 							break;
763 						foreach (i, TV; T.Types) {
764 							enum fieldName = underscoreStrip(T.fieldNames[i]);
765 							alias STraits = SubTraits!(Traits, TV);
766 							case fieldName: {
767 								ser.beginReadDictionaryEntry!STraits(fieldName);
768 								ret[i] = ser.deserializeValue!(TV, ATTRIBUTES);
769 								ser.endReadDictionaryEntry!STraits(fieldName);
770 								set[i] = true;
771 							} break;
772 						}
773 					}
774 				});
775 				foreach (i, fieldName; T.fieldNames)
776 					enforce(set[i], "Missing tuple field '"~fieldName~"' of type '"~T.Types[i].stringof~"' ("~Policy.stringof~").");
777 				return ret;
778 			} else static if (fieldsCount == 1) {
779 				return T(ser.deserializeValue!(T.Types[0], ATTRIBUTES)());
780 			} else {
781 				T ret;
782 				size_t currentField = 0;
783 				ser.readArray!Traits((sz) { assert(sz == 0 || sz == fieldsCount); }, {
784 					switch (currentField++) {
785 						default: break;
786 						foreach (i, TV; T.Types) {
787 							alias STraits = SubTraits!(Traits, TV);
788 							case i: {
789 								ser.beginReadArrayEntry!STraits(i);
790 								ret[i] = ser.deserializeValue!(TV, ATTRIBUTES);
791 								ser.endReadArrayEntry!STraits(i);
792 							} break;
793 						}
794 					}
795 				});
796 				enforce(currentField == fieldsCount, "Missing tuple field(s) - expected '"~fieldsCount.stringof~"', received '"~currentField.stringof~"' ("~Policy.stringof~").");
797 				return ret;
798 			}
799 		} else static if (isStaticArray!T) {
800 			alias TV = typeof(T.init[0]);
801 			alias STraits = SubTraits!(Traits, TV);
802 			T ret;
803 			size_t i = 0;
804 			ser.readArray!Traits((sz) { assert(sz == 0 || sz == T.length); }, {
805 				assert(i < T.length);
806 				ser.beginReadArrayEntry!STraits(i);
807 				ret[i] = ser.deserializeValue!(TV, ATTRIBUTES);
808 				ser.endReadArrayEntry!STraits(i);
809 				i++;
810 			});
811 			return ret;
812 		} else static if (isDynamicArray!T) {
813 			alias TV = typeof(T.init[0]);
814 			alias STraits = SubTraits!(Traits, TV);
815 			//auto ret = appender!T();
816 			T ret; // Cannot use appender because of DMD BUG 10690/10859/11357
817 			ser.readArray!Traits((sz) @safe { ret.reserve(sz); }, () @safe {
818 				size_t i = ret.length;
819 				ser.beginReadArrayEntry!STraits(i);
820 				static if (__traits(compiles, () @safe { ser.deserializeValue!(TV, ATTRIBUTES); }))
821 					ret ~= ser.deserializeValue!(TV, ATTRIBUTES);
822 				else // recursive array https://issues.dlang.org/show_bug.cgi?id=16528
823 					ret ~= (() @trusted => ser.deserializeValue!(TV, ATTRIBUTES))();
824 				ser.endReadArrayEntry!STraits(i);
825 			});
826 			return ret;//cast(T)ret.data;
827 		} else static if (isAssociativeArray!T) {
828 			alias TK = KeyType!T;
829 			alias TV = ValueType!T;
830 			alias STraits = SubTraits!(Traits, TV);
831 
832 			T ret;
833 			ser.readDictionary!Traits((name) @safe {
834 				TK key;
835 				static if (is(TK == string) || (is(TK == enum) && is(OriginalType!TK == string))) key = cast(TK)name;
836 				else static if (is(TK : real) || is(TK : long) || is(TK == enum)) key = name.to!TK;
837 				else static if (isStringSerializable!TK) key = TK.fromString(name);
838 				else static assert(false, "Associative array keys must be strings, numbers, enums, or have toString/fromString methods.");
839 				ser.beginReadDictionaryEntry!STraits(name);
840 				ret[key] = ser.deserializeValue!(TV, ATTRIBUTES);
841 				ser.endReadDictionaryEntry!STraits(name);
842 			});
843 			return ret;
844 		} else static if (isInstanceOf!(Nullable, T)) {
845 			if (ser.tryReadNull!Traits()) return T.init;
846 			return T(ser.deserializeValue!(typeof(T.init.get()), ATTRIBUTES));
847 		} else static if (isInstanceOf!(Typedef, T)) {
848 			return T(ser.deserializeValue!(TypedefType!T, ATTRIBUTES));
849 		} else static if (is(T == BitFlags!E, E)) {
850 			alias STraits = SubTraits!(Traits, E);
851 			T ret;
852 			size_t i = 0;
853 			ser.readArray!Traits((sz) {}, {
854 				ser.beginReadArrayEntry!STraits(i);
855 				ret |= ser.deserializeValue!(E, ATTRIBUTES);
856 				ser.endReadArrayEntry!STraits(i);
857 				i++;
858 			});
859 			return ret;
860 		} else static if (isCustomSerializable!T) {
861 			alias CustomType = typeof(T.init.toRepresentation());
862 			return T.fromRepresentation(ser.deserializeValue!(CustomType, ATTRIBUTES));
863 		} else static if (isISOExtStringSerializable!T) {
864 			return T.fromISOExtString(ser.readValue!(Traits, string)());
865 		} else static if (isStringSerializable!T) {
866 			return T.fromString(ser.readValue!(Traits, string)());
867 		} else static if (is(T == struct) || is(T == class)) {
868 			static if (is(T == class)) {
869 				if (ser.tryReadNull!Traits()) return null;
870 			}
871 
872 			T ret;
873 			string name;
874 			bool[getExpandedFieldsData!(T, SerializableFields!(T, Policy)).length] set;
875 			static if (is(T == class)) ret = new T;
876 
877 			void safeSetMember(string mname, U)(ref T value, U fval)
878 			@safe {
879 				static if (__traits(compiles, () @safe { __traits(getMember, value, mname) = fval; }))
880 					__traits(getMember, value, mname) = fval;
881 				else {
882 					pragma(msg, "Warning: Setter for "~fullyQualifiedName!T~"."~mname~" is not @safe");
883 					() @trusted { __traits(getMember, value, mname) = fval; } ();
884 				}
885 			}
886 
887 			static if (hasPolicyAttributeL!(AsArrayAttribute, Policy, ATTRIBUTES)) {
888 				size_t idx = 0;
889 				ser.readArray!Traits((sz){}, {
890 					static if (hasSerializableFields!(T, Policy)) {
891 						switch (idx++) {
892 							default: break;
893 							foreach (i, FD; getExpandedFieldsData!(T, SerializableFields!(T, Policy))) {
894 								enum mname = FD[0];
895 								enum msindex = FD[1];
896 								alias MT = AliasSeq!(__traits(getMember, T, mname));
897 								alias MTI = MT[msindex];
898 								alias TMTI = typeof(MTI);
899 								alias TMTIA = AliasSeq!(__traits(getAttributes, MTI));
900 								alias STraits = SubTraits!(Traits, TMTI, TMTIA);
901 
902 							case i:
903 								static if (hasPolicyAttribute!(OptionalAttribute, Policy, MTI))
904 									if (ser.tryReadNull!STraits()) return;
905 								set[i] = true;
906 								ser.beginReadArrayEntry!STraits(i);
907 								static if (!isBuiltinTuple!(T, mname)) {
908 									safeSetMember!mname(ret, ser.deserializeValue!(TMTI, TMTIA));
909 								} else {
910 									__traits(getMember, ret, mname)[msindex] = ser.deserializeValue!(TMTI, TMTIA);
911 								}
912 								ser.endReadArrayEntry!STraits(i);
913 								break;
914 							}
915 						}
916 					} else {
917 						pragma(msg, "Deserializing composite type "~T.stringof~" which has no serializable fields.");
918 					}
919 				});
920 			} else {
921 				ser.readDictionary!Traits((name) {
922 					static if (hasSerializableFields!(T, Policy)) {
923 						switch (name) {
924 							default:
925 								static if (is(typeof(ser.skipValue()))) {
926 									ser.skipValue();
927 								}
928 								break;
929 							foreach (i, mname; SerializableFields!(T, Policy)) {
930 								alias TM = AliasSeq!(typeof(__traits(getMember, T, mname)));
931 								static if (__traits(getOverloads, T, mname).length > 0) {
932 									// combine attributes of overload sets
933 									alias getAtts(alias ovl) = __traits(getAttributes, ovl);
934 									alias TA = staticMap!(getAtts, __traits(getOverloads, T, mname));
935 								} else {
936 									alias TA = AliasSeq!(__traits(getAttributes, AliasSeq!(__traits(getMember, T, mname))[0]));
937 								}
938 								alias STraits = SubTraits!(Traits, TM, TA);
939 								enum fname = getPolicyAttribute!(fullyQualifiedName!T~"."~mname, NameAttribute, Policy, TA)(NameAttribute!DefaultPolicy(underscoreStrip(mname))).name;
940 								case fname:
941 									static if (hasPolicyAttribute!(OptionalAttribute, Policy, AliasSeq!(__traits(getMember, T, mname))[0]))
942 										if (ser.tryReadNull!STraits()) return;
943 									set[i] = true;
944 									ser.beginReadDictionaryEntry!STraits(fname);
945 									static if (!isBuiltinTuple!(T, mname)) {
946 										safeSetMember!mname(ret, ser.deserializeValue!(TM, TA));
947 									} else {
948 										__traits(getMember, ret, mname) = ser.deserializeValue!(Tuple!TM, TA);
949 									}
950 									ser.endReadDictionaryEntry!STraits(fname);
951 									break;
952 							}
953 						}
954 					} else {
955 						pragma(msg, "Deserializing composite type "~T.stringof~" which has no serializable fields.");
956 					}
957 				});
958 			}
959 			foreach (i, mname; SerializableFields!(T, Policy))
960 				static if (!hasPolicyAttribute!(OptionalAttribute, Policy, AliasSeq!(__traits(getMember, T, mname))[0]))
961 					enforce(set[i], "Missing non-optional field '"~mname~"' of type '"~T.stringof~"' ("~Policy.stringof~").");
962 			return ret;
963 		} else static if (isPointer!T) {
964 			if (ser.tryReadNull!Traits()) return null;
965 			alias PT = PointerTarget!T;
966 			auto ret = new PT;
967 			*ret = ser.deserializeValue!(PT, ATTRIBUTES);
968 			return ret;
969 		} else static if (is(T == bool) || is(T : real) || is(T : long)) {
970 			return to!T(ser.deserializeValue!string());
971 		} else static assert(false, "Unsupported serialization type: " ~ T.stringof);
972 	}
973 }
974 
975 
976 /**
977 	Attribute for overriding the field name during (de-)serialization.
978 
979 	Note that without the `@name` attribute there is a shorter alternative
980 	for using names that collide with a D keyword. A single trailing
981 	underscore will automatically be stripped when determining a field
982 	name.
983 */
984 NameAttribute!Policy name(alias Policy = DefaultPolicy)(string name)
985 {
986 	return NameAttribute!Policy(name);
987 }
988 ///
989 unittest {
990 	struct CustomPolicy {}
991 
992 	struct Test {
993 		// serialized as "screen-size":
994 		@name("screen-size") int screenSize;
995 
996 		// serialized as "print-size" by default,
997 		// but as "PRINTSIZE" if CustomPolicy is used for serialization.
998 		@name("print-size")
999 		@name!CustomPolicy("PRINTSIZE")
1000 		int printSize;
1001 
1002 		// serialized as "version"
1003 		int version_;
1004 	}
1005 }
1006 
1007 
1008 /**
1009 	Attribute marking a field as optional during deserialization.
1010 */
1011 @property OptionalAttribute!Policy optional(alias Policy = DefaultPolicy)()
1012 {
1013 	return OptionalAttribute!Policy();
1014 }
1015 ///
1016 unittest {
1017 	struct Test {
1018 		// does not need to be present during deserialization
1019 		@optional int screenSize = 100;
1020 	}
1021 }
1022 
1023 
1024 /**
1025 	Attribute for marking non-serialized fields.
1026 */
1027 @property IgnoreAttribute!Policy ignore(alias Policy = DefaultPolicy)()
1028 {
1029 	return IgnoreAttribute!Policy();
1030 }
1031 ///
1032 unittest {
1033 	struct Test {
1034 		// is neither serialized not deserialized
1035 		@ignore int screenSize;
1036 	}
1037 }
1038 ///
1039 unittest {
1040 	template CustomPolicy(T) {
1041 		// ...
1042 	}
1043 
1044 	struct Test {
1045 		// not (de)serialized for serializeWithPolicy!(Test, CustomPolicy)
1046 		// but for other policies or when serialized without a policy
1047 		@ignore!CustomPolicy int screenSize;
1048 	}
1049 }
1050 
1051 
1052 /**
1053 	Attribute for forcing serialization of enum fields by name instead of by value.
1054 */
1055 @property ByNameAttribute!Policy byName(alias Policy = DefaultPolicy)()
1056 {
1057 	return ByNameAttribute!Policy();
1058 }
1059 ///
1060 unittest {
1061 	enum Color {
1062 		red,
1063 		green,
1064 		blue
1065 	}
1066 
1067 	struct Test {
1068 		// serialized as an int (e.g. 1 for Color.green)
1069 		Color color;
1070 		// serialized as a string (e.g. "green" for Color.green)
1071 		@byName Color namedColor;
1072 		// serialized as array of ints
1073 		Color[] colorArray;
1074 		// serialized as array of strings
1075 		@byName Color[] namedColorArray;
1076 	}
1077 }
1078 
1079 
1080 /**
1081 	Attribute for representing a struct/class as an array instead of an object.
1082 
1083 	Usually structs and class objects are serialized as dictionaries mapping
1084 	from field name to value. Using this attribute, they will be serialized
1085 	as a flat array instead. Note that changing the layout will make any
1086 	already serialized data mismatch when this attribute is used.
1087 */
1088 @property AsArrayAttribute!Policy asArray(alias Policy = DefaultPolicy)()
1089 {
1090 	return AsArrayAttribute!Policy();
1091 }
1092 ///
1093 unittest {
1094 	struct Fields {
1095 		int f1;
1096 		string f2;
1097 		double f3;
1098 	}
1099 
1100 	struct Test {
1101 		// serialized as name:value pairs ["f1": int, "f2": string, "f3": double]
1102 		Fields object;
1103 		// serialized as a sequential list of values [int, string, double]
1104 		@asArray Fields array;
1105 	}
1106 
1107 	import dub.internal.vibecompat.data.json;
1108 	static assert(is(typeof(serializeToJson(Test()))));
1109 }
1110 
1111 
1112 /**
1113 	Makes this nullable as if it is not a nullable to the serializer. Ignores the field completely when it is null.
1114 
1115 	Works with Nullable!classes and Nullable!structs. Behavior is undefined if this is applied to other types.
1116 
1117 	Implicitly marks this as optional for deserialization. (Keeps the struct default value when not present in serialized value)
1118 */
1119 @property EmbedNullableIgnoreNullAttribute!Policy embedNullable(alias Policy = DefaultPolicy)()
1120 {
1121 	return EmbedNullableIgnoreNullAttribute!Policy();
1122 }
1123 ///
1124 unittest {
1125 	import std.typecons : Nullable;
1126 
1127 	struct Test {
1128 		// Not serialized at all if null, ignored on deserialization if not present.
1129 		@embedNullable Nullable!int field;
1130 	}
1131 }
1132 
1133 
1134 ///
1135 enum FieldExistence
1136 {
1137 	missing,
1138 	exists,
1139 	defer
1140 }
1141 
1142 /// User defined attribute (not intended for direct use)
1143 struct NameAttribute(alias POLICY) { alias Policy = POLICY; string name; }
1144 /// ditto
1145 struct OptionalAttribute(alias POLICY) { alias Policy = POLICY; }
1146 /// ditto
1147 struct IgnoreAttribute(alias POLICY) { alias Policy = POLICY; }
1148 /// ditto
1149 struct ByNameAttribute(alias POLICY) { alias Policy = POLICY; }
1150 /// ditto
1151 struct AsArrayAttribute(alias POLICY) { alias Policy = POLICY; }
1152 /// ditto
1153 struct EmbedNullableIgnoreNullAttribute(alias POLICY) { alias Policy = POLICY; }
1154 
1155 /**
1156 	Checks if a given type has a custom serialization representation.
1157 
1158 	A class or struct type is custom serializable if it defines a pair of
1159 	`toRepresentation`/`fromRepresentation` methods. Any class or
1160 	struct type that has this trait will be serialized by using the return
1161 	value of it's `toRepresentation` method instead of the original value.
1162 
1163 	This trait has precedence over `isISOExtStringSerializable` and
1164 	`isStringSerializable`.
1165 */
1166 template isCustomSerializable(T)
1167 {
1168 	enum bool isCustomSerializable = is(typeof(T.init.toRepresentation())) && is(typeof(T.fromRepresentation(T.init.toRepresentation())) == T);
1169 }
1170 ///
1171 unittest {
1172 	// represented as a single uint when serialized
1173 	static struct S {
1174 		ushort x, y;
1175 
1176 		uint toRepresentation() const { return x + (y << 16); }
1177 		static S fromRepresentation(uint i) { return S(i & 0xFFFF, i >> 16); }
1178 	}
1179 
1180 	static assert(isCustomSerializable!S);
1181 }
1182 
1183 
1184 /**
1185 	Checks if a given type has an ISO extended string serialization representation.
1186 
1187 	A class or struct type is ISO extended string serializable if it defines a
1188 	pair of `toISOExtString`/`fromISOExtString` methods. Any class or
1189 	struct type that has this trait will be serialized by using the return
1190 	value of it's `toISOExtString` method instead of the original value.
1191 
1192 	This is mainly useful for supporting serialization of the the date/time
1193 	types in `std.datetime`.
1194 
1195 	This trait has precedence over `isStringSerializable`.
1196 */
1197 template isISOExtStringSerializable(T)
1198 {
1199 	enum bool isISOExtStringSerializable = is(typeof(T.init.toISOExtString()) : string) && is(typeof(T.fromISOExtString("")) : T);
1200 }
1201 ///
1202 unittest {
1203 	import std.datetime;
1204 
1205 	static assert(isISOExtStringSerializable!DateTime);
1206 	static assert(isISOExtStringSerializable!SysTime);
1207 
1208 	// represented as an ISO extended string when serialized
1209 	static struct S {
1210 		// dummy example implementations
1211 		string toISOExtString() const { return ""; }
1212 		static S fromISOExtString(string s) { return S.init; }
1213 	}
1214 
1215 	static assert(isISOExtStringSerializable!S);
1216 }
1217 
1218 
1219 /**
1220 	Checks if a given type has a string serialization representation.
1221 
1222 	A class or struct type is string serializable if it defines a pair of
1223 	`toString`/`fromString` methods. Any class or struct type that
1224 	has this trait will be serialized by using the return value of it's
1225 	`toString` method instead of the original value.
1226 */
1227 template isStringSerializable(T)
1228 {
1229 	enum bool isStringSerializable = is(typeof(T.init.toString()) : string) && is(typeof(T.fromString("")) : T);
1230 }
1231 ///
1232 unittest {
1233 	import std.conv;
1234 
1235 	// represented as a string when serialized
1236 	static struct S {
1237 		int value;
1238 
1239 		// dummy example implementations
1240 		string toString() const { return value.to!string(); }
1241 		static S fromString(string s) { return S(s.to!int()); }
1242 	}
1243 
1244 	static assert(isStringSerializable!S);
1245 }
1246 
1247 
1248 /** Default policy (performs no customization).
1249 */
1250 template DefaultPolicy(T)
1251 {
1252 }
1253 
1254 /**
1255 	Checks if a given policy supports custom serialization for a given type.
1256 
1257 	A class or struct type is custom serializable according to a policy if
1258 	the policy defines a pair of `toRepresentation`/`fromRepresentation`
1259 	functions. Any class or struct type that has this trait for the policy supplied to
1260 	`serializeWithPolicy` will be serialized by using the return value of the
1261 	policy `toRepresentation` function instead of the original value.
1262 
1263 	This trait has precedence over `isCustomSerializable`,
1264 	`isISOExtStringSerializable` and `isStringSerializable`.
1265 
1266 	See_Also: `vibe.data.serialization.serializeWithPolicy`
1267 */
1268 template isPolicySerializable(alias Policy, T)
1269 {
1270 	enum bool isPolicySerializable = is(typeof(Policy!T.toRepresentation(T.init))) &&
1271 		is(typeof(Policy!T.fromRepresentation(Policy!T.toRepresentation(T.init))) : T);
1272 }
1273 ///
1274 unittest {
1275 	import std.conv;
1276 
1277 	// represented as the boxed value when serialized
1278 	static struct Box(T) {
1279 		T value;
1280 	}
1281 
1282 	template BoxPol(S)
1283 	{
1284 		auto toRepresentation(S s) {
1285 			return s.value;
1286 		}
1287 
1288 		S fromRepresentation(typeof(S.init.value) v) {
1289 			return S(v);
1290 		}
1291 	}
1292 	static assert(isPolicySerializable!(BoxPol, Box!int));
1293 }
1294 
1295 
1296 /**
1297 	Chains serialization policy.
1298 
1299 	Constructs a serialization policy that given a type `T` will apply the
1300 	first compatible policy `toRepresentation` and `fromRepresentation`
1301 	functions. Policies are evaluated left-to-right according to
1302 	`isPolicySerializable`.
1303 
1304 	See_Also: `vibe.data.serialization.serializeWithPolicy`
1305 */
1306 template ChainedPolicy(alias Primary, Fallbacks...)
1307 {
1308 	static if (Fallbacks.length == 0) {
1309 		alias ChainedPolicy = Primary;
1310 	} else {
1311 		alias ChainedPolicy = ChainedPolicy!(ChainedPolicyImpl!(Primary, Fallbacks[0]), Fallbacks[1..$]);
1312 	}
1313 }
1314 ///
1315 unittest {
1316 	import std.conv;
1317 
1318 	// To be represented as the boxed value when serialized
1319 	static struct Box(T) {
1320 		T value;
1321 	}
1322 	// Also to berepresented as the boxed value when serialized, but has
1323 	// a different way to access the value.
1324 	static struct Box2(T) {
1325 		private T v;
1326 		ref T get() {
1327 			return v;
1328 		}
1329 	}
1330 	template BoxPol(S)
1331 	{
1332 		auto toRepresentation(S s) {
1333 			return s.value;
1334 		}
1335 
1336 		S fromRepresentation(typeof(toRepresentation(S.init)) v) {
1337 			return S(v);
1338 		}
1339 	}
1340 	template Box2Pol(S)
1341 	{
1342 		auto toRepresentation(S s) {
1343 			return s.get();
1344 		}
1345 
1346 		S fromRepresentation(typeof(toRepresentation(S.init)) v) {
1347 			S s;
1348 			s.get() = v;
1349 			return s;
1350 		}
1351 	}
1352 	alias ChainPol = ChainedPolicy!(BoxPol, Box2Pol);
1353 	static assert(!isPolicySerializable!(BoxPol, Box2!int));
1354 	static assert(!isPolicySerializable!(Box2Pol, Box!int));
1355 	static assert(isPolicySerializable!(ChainPol, Box!int));
1356 	static assert(isPolicySerializable!(ChainPol, Box2!int));
1357 }
1358 
1359 private template ChainedPolicyImpl(alias Primary, alias Fallback)
1360 {
1361 	template Pol(T)
1362 	{
1363 		static if (isPolicySerializable!(Primary, T)) {
1364 			alias toRepresentation = Primary!T.toRepresentation;
1365 			alias fromRepresentation = Primary!T.fromRepresentation;
1366 		} else {
1367 			alias toRepresentation = Fallback!T.toRepresentation;
1368 			alias fromRepresentation = Fallback!T.fromRepresentation;
1369 		}
1370 	}
1371 	alias ChainedPolicyImpl = Pol;
1372 }
1373 
1374 private template isBuiltinTuple(T, string member)
1375 {
1376     alias TM = AliasSeq!(typeof(__traits(getMember, T.init, member)));
1377     static if (TM.length > 1) enum isBuiltinTuple = true;
1378     else static if (is(typeof(__traits(getMember, T.init, member)) == TM[0]))
1379         enum isBuiltinTuple = false;
1380     else enum isBuiltinTuple = true; // single-element tuple
1381 }
1382 
1383 // heuristically determines @safe'ty of the serializer by testing readValue and writeValue for type int
1384 private template isSafeSerializer(S)
1385 {
1386 	alias T = Traits!(int, DefaultPolicy);
1387 	static if (__traits(hasMember, S, "writeValue"))
1388 		enum isSafeSerializer = __traits(compiles, (S s) @safe { s.writeValue!T(42); });
1389 	else static assert(0, "Serializer is missing required writeValue method");
1390 }
1391 
1392 // heuristically determines @safe'ty of the deserializer by testing readValue and writeValue for type int
1393 private template isSafeDeserializer(S)
1394 {
1395 	alias T = Traits!(int, DefaultPolicy);
1396 	static if (__traits(hasMember, S, "readValue"))
1397 		enum isSafeDeserializer = __traits(compiles, (S s) @safe { s.readValue!(T, int)(); });
1398 	else static assert(0, "Deserializer is missing required readValue method");
1399 }
1400 
1401 private template hasAttribute(T, alias decl) {
1402 	// detect overload sets and satisfy if any overload has the attribute
1403 	static if (is(typeof(__traits(getMember, __traits(parent, decl), __traits(identifier, decl))))
1404 		&& __traits(getOverloads, __traits(parent, decl), __traits(identifier, decl)).length > 1)
1405 	{
1406 		enum match(alias ovl) = findFirstUDA!(T, ovl).found;
1407 		enum hasAttribute = anySatisfy!(match, __traits(getOverloads, __traits(parent, decl), __traits(identifier, decl)));
1408 	} else {
1409 		enum hasAttribute = findFirstUDA!(T, decl).found;
1410 	}
1411 }
1412 
1413 unittest {
1414 	@asArray int i1;
1415 	static assert(hasAttribute!(AsArrayAttribute!DefaultPolicy, i1));
1416 	int i2;
1417 	static assert(!hasAttribute!(AsArrayAttribute!DefaultPolicy, i2));
1418 
1419 	static struct S {
1420 		@asArray void foo(int) {}
1421 		void foo() {}
1422 
1423 		void bar(int) {}
1424 		@asArray void bar() {}
1425 	}
1426 
1427 	static assert(hasAttribute!(AsArrayAttribute!DefaultPolicy, S.foo));
1428 	static assert(hasAttribute!(AsArrayAttribute!DefaultPolicy, S.bar));
1429 }
1430 
1431 private template hasPolicyAttribute(alias T, alias POLICY, alias decl)
1432 {
1433 	// __traits(identifier) to hack around T being a template and not a type
1434 	// this if makes hasPolicyAttribute!(OptionalAttribute) == true when EmbedNullableIgnoreNullAttribute is present.
1435 	static if (__traits(identifier, T) == __traits(identifier, OptionalAttribute))
1436 		enum hasPolicyAttribute = hasPolicyAttributeImpl!(T, POLICY, decl)
1437 			|| hasPolicyAttributeImpl!(EmbedNullableIgnoreNullAttribute, POLICY, decl);
1438 	else
1439 		enum hasPolicyAttribute = hasPolicyAttributeImpl!(T, POLICY, decl);
1440 }
1441 
1442 private template hasPolicyAttributeImpl(alias T, alias POLICY, alias decl)
1443 {
1444 	enum hasPolicyAttributeImpl = hasAttribute!(T!POLICY, decl) || hasAttribute!(T!DefaultPolicy, decl);
1445 }
1446 
1447 unittest {
1448 	import std.typecons : Nullable;
1449 
1450 	template CP(T) {}
1451 	@asArray!CP int i1;
1452 	@asArray int i2;
1453 	int i3;
1454 	@embedNullable Nullable!int i4;
1455 
1456 	static assert(hasPolicyAttribute!(AsArrayAttribute, CP, i1));
1457 	static assert(hasPolicyAttribute!(AsArrayAttribute, CP, i2));
1458 	static assert(!hasPolicyAttribute!(AsArrayAttribute, CP, i3));
1459 	static assert(!hasPolicyAttribute!(AsArrayAttribute, DefaultPolicy, i1));
1460 	static assert(hasPolicyAttribute!(AsArrayAttribute, DefaultPolicy, i2));
1461 	static assert(!hasPolicyAttribute!(AsArrayAttribute, DefaultPolicy, i3));
1462 	static assert(hasPolicyAttribute!(EmbedNullableIgnoreNullAttribute, DefaultPolicy, i4));
1463 	static assert(hasPolicyAttribute!(OptionalAttribute, DefaultPolicy, i4));
1464 	static assert(!hasPolicyAttribute!(IgnoreAttribute, DefaultPolicy, i4));
1465 }
1466 
1467 
1468 private template hasAttributeL(T, ATTRIBUTES...) {
1469 	static if (ATTRIBUTES.length == 1) {
1470 		enum hasAttributeL = is(typeof(ATTRIBUTES[0]) == T);
1471 	} else static if (ATTRIBUTES.length > 1) {
1472 		enum hasAttributeL = hasAttributeL!(T, ATTRIBUTES[0 .. $/2]) || hasAttributeL!(T, ATTRIBUTES[$/2 .. $]);
1473 	} else {
1474 		enum hasAttributeL = false;
1475 	}
1476 }
1477 
1478 unittest {
1479 	static assert(hasAttributeL!(AsArrayAttribute!DefaultPolicy, byName, asArray));
1480 	static assert(!hasAttributeL!(AsArrayAttribute!DefaultPolicy, byName));
1481 }
1482 
1483 private template hasPolicyAttributeL(alias T, alias POLICY, ATTRIBUTES...)
1484 {
1485 	enum hasPolicyAttributeL = hasAttributeL!(T!POLICY, ATTRIBUTES) || hasAttributeL!(T!DefaultPolicy, ATTRIBUTES);
1486 }
1487 
1488 private template TypeAttributes(T) {
1489 	static if (__traits(compiles, __traits(identifier, T)))
1490 		alias TypeAttributes = AliasSeq!(__traits(getAttributes, T));
1491 	else  // D < 2.101.0 raises an error when attempting to get attributes of built-in types.
1492 		alias TypeAttributes = AliasSeq!();
1493 }
1494 
1495 private static auto getPolicyAttribute(string field, alias Attribute, alias Policy, Attributes...)(Attribute!DefaultPolicy default_value)
1496 {
1497 	enum match(alias A) = matchesUDAKind!(A, Attribute!Policy);
1498 	enum PA = Filter!(match, Attributes);
1499 	static if (PA.length) {
1500 		static foreach (i; 1 .. PA.length)
1501 			static assert(PA[i] == PA[0], "Mismatching " ~ Attribute!Policy.stringof ~ " attributes for serialized field " ~ field);
1502 		return PA[0];
1503 	} else {
1504 		enum matchDef(alias A) = matchesUDAKind(A, Attribute!DefaultPolicy);
1505 		enum PAD = Filter!(match, Attributes);
1506 		static if (PAD.length) {
1507 			static foreach (i; 1 .. PAD.length)
1508 				static assert(PAD[i] == PAD[0], "Mismatching " ~ Attribute!DefaultPolicy.stringof ~ " attributes for serialized field " ~ field);
1509 			return PAD[0];
1510 		} else return default_value;
1511 	}
1512 }
1513 
1514 private string underscoreStrip(string field_name)
1515 @safe nothrow @nogc {
1516 	if( field_name.length < 1 || field_name[$-1] != '_' ) return field_name;
1517 	else return field_name[0 .. $-1];
1518 }
1519 
1520 
1521 private template hasSerializableFields(T, alias POLICY, size_t idx = 0)
1522 {
1523 	enum hasSerializableFields = SerializableFields!(T, POLICY).length > 0;
1524 	/*static if (idx < __traits(allMembers, T).length) {
1525 		enum mname = __traits(allMembers, T)[idx];
1526 		static if (!isRWPlainField!(T, mname) && !isRWField!(T, mname)) enum hasSerializableFields = hasSerializableFields!(T, idx+1);
1527 		else static if (hasAttribute!(IgnoreAttribute, __traits(getMember, T, mname))) enum hasSerializableFields = hasSerializableFields!(T, idx+1);
1528 		else enum hasSerializableFields = true;
1529 	} else enum hasSerializableFields = false;*/
1530 }
1531 
1532 private template SerializableFields(COMPOSITE, alias POLICY)
1533 {
1534 	alias SerializableFields = FilterSerializableFields!(COMPOSITE, POLICY, __traits(allMembers, COMPOSITE));
1535 }
1536 
1537 private template FilterSerializableFields(COMPOSITE, alias POLICY, FIELDS...)
1538 {
1539 	static if (FIELDS.length > 1) {
1540 		alias FilterSerializableFields = AliasSeq!(
1541 			FilterSerializableFields!(COMPOSITE, POLICY, FIELDS[0 .. $/2]),
1542 			FilterSerializableFields!(COMPOSITE, POLICY, FIELDS[$/2 .. $]));
1543 	} else static if (FIELDS.length == 1) {
1544 		alias T = COMPOSITE;
1545 		enum mname = FIELDS[0];
1546 		static if (isRWPlainField!(T, mname) || isRWField!(T, mname)) {
1547 			alias Tup = AliasSeq!(__traits(getMember, COMPOSITE, FIELDS[0]));
1548 			static if (Tup.length != 1) {
1549 				alias FilterSerializableFields = AliasSeq!(mname);
1550 			} else static if (__traits(getOverloads, T, mname).length > 1) {
1551 				enum ignored(alias sym) = hasPolicyAttribute!(IgnoreAttribute, POLICY, sym);
1552 				static if (!anySatisfy!(ignored, __traits(getOverloads, T, mname)))
1553 					alias FilterSerializableFields = AliasSeq!(mname);
1554 				else
1555 					alias FilterSerializableFields = AliasSeq!();
1556 			} else {
1557 				static if (!hasPolicyAttribute!(IgnoreAttribute, POLICY, __traits(getMember, T, mname)))
1558 					alias FilterSerializableFields = AliasSeq!(mname);
1559 				else
1560 					alias FilterSerializableFields = AliasSeq!();
1561 			}
1562 		} else alias FilterSerializableFields = AliasSeq!();
1563 	} else alias FilterSerializableFields = AliasSeq!();
1564 }
1565 
1566 private size_t getExpandedFieldCount(T, FIELDS...)()
1567 {
1568 	size_t ret = 0;
1569 	foreach (F; FIELDS) ret += AliasSeq!(__traits(getMember, T, F)).length;
1570 	return ret;
1571 }
1572 
1573 private template getExpandedFieldsData(T, FIELDS...)
1574 {
1575 	import std.meta : aliasSeqOf, staticMap;
1576 	import std.range : repeat, zip, iota;
1577 
1578 	enum subfieldsCount(alias F) = AliasSeq!(__traits(getMember, T, F)).length;
1579 	alias processSubfield(alias F) = aliasSeqOf!(zip(repeat(F), iota(subfieldsCount!F)));
1580 	alias getExpandedFieldsData = staticMap!(processSubfield, FIELDS);
1581 }
1582 
1583 /// Uses Base64 representation for `ubyte[]` instead of `to!string`
1584 public class Base64ArrayPolicy (R) if (isArray!R && is(ElementType!R : ubyte))
1585 {
1586 	public static string toRepresentation (in R data) @safe pure
1587 	{
1588 		import std.base64 : Base64;
1589 		return Base64.encode(data);
1590 	}
1591 
1592 	public static ubyte[] fromRepresentation (in string data) @safe pure
1593 	{
1594 		import std.base64 : Base64;
1595 		return Base64.decode(data);
1596 	}
1597 }
1598 
1599 /******************************************************************************/
1600 /* General serialization unit testing                                         */
1601 /******************************************************************************/
1602 
1603 version (unittest) {
1604 	static assert(isSafeSerializer!TestSerializer);
1605 	static assert(isSafeDeserializer!TestSerializer);
1606 
1607 	private struct TestSerializer {
1608 		import std.array, std.conv, std.range, std.string, std.typecons;
1609 
1610 		string result;
1611 
1612 		enum isSupportedValueType(T) = is(T == string) || is(T == typeof(null)) || is(T == float) || is (T == int);
1613 
1614 		template unqualSeq(Specs...)
1615 		{
1616 			static if (Specs.length == 0) alias unqualSeq = AliasSeq!();
1617 			else static if (is(Specs[0])) alias unqualSeq = AliasSeq!(Unqual!(Specs[0]), unqualSeq!(Specs[1 .. $]));
1618 			else alias unqualSeq = AliasSeq!(Specs[0], unqualSeq!(Specs[1 .. $]));
1619 		}
1620 
1621 		template unqualType(T) {
1622 			static if (isAssociativeArray!T) alias unqualType = Unqual!(ValueType!T)[Unqual!(KeyType!T)];
1623 			else static if (isTuple!T) alias unqualType = Tuple!(unqualSeq!(TemplateArgsOf!T));
1624 			else static if (isArray!T && !isSomeString!T) alias unqualType = Unqual!(ElementType!T)[];
1625 			else alias unqualType = Unqual!T;
1626 		}
1627 
1628 		string getSerializedResult() @safe { return result; }
1629 		void beginWriteDictionary(Traits)() { result ~= "D("~unqualType!(Traits.Type).mangleof~"){"; }
1630 		void endWriteDictionary(Traits)() { result ~= "}D("~unqualType!(Traits.Type).mangleof~")"; }
1631 		void beginWriteDictionaryEntry(Traits)(string name) { result ~= "DE("~unqualType!(Traits.Type).mangleof~","~name~")("; }
1632 		void endWriteDictionaryEntry(Traits)(string name) { result ~= ")DE("~unqualType!(Traits.Type).mangleof~","~name~")"; }
1633 		void beginWriteArray(Traits)(size_t length) { result ~= "A("~unqualType!(Traits.Type).mangleof~")["~length.to!string~"]["; }
1634 		void endWriteArray(Traits)() { result ~= "]A("~unqualType!(Traits.Type).mangleof~")"; }
1635 		void beginWriteArrayEntry(Traits)(size_t i) { result ~= "AE("~unqualType!(Traits.Type).mangleof~","~i.to!string~")("; }
1636 		void endWriteArrayEntry(Traits)(size_t i) { result ~= ")AE("~unqualType!(Traits.Type).mangleof~","~i.to!string~")"; }
1637 		void writeValue(Traits, T)(T value) {
1638 			if (is(T == typeof(null))) result ~= "null";
1639 			else {
1640 				assert(isSupportedValueType!(unqualType!T));
1641 				result ~= "V("~(unqualType!T).mangleof~")("~value.to!string~")";
1642 			}
1643 		}
1644 
1645 		// deserialization
1646 		void readDictionary(Traits)(scope void delegate(string) @safe entry_callback)
1647 		{
1648 			skip("D("~unqualType!(Traits.Type).mangleof~"){");
1649 			while (result.startsWith("DE(")) {
1650 				result = result[3 .. $];
1651 				auto idx = result.indexOf(',');
1652 				auto idx2 = result.indexOf(")(");
1653 				assert(idx > 0 && idx2 > idx);
1654 				auto t = result[0 .. idx];
1655 				auto n = result[idx+1 .. idx2];
1656 				result = result[idx2+2 .. $];
1657 				entry_callback(n);
1658 				skip(")DE("~t~","~n~")");
1659 			}
1660 			skip("}D("~unqualType!(Traits.Type).mangleof~")");
1661 		}
1662 
1663 		void beginReadDictionaryEntry(Traits)(string name) {}
1664 		void endReadDictionaryEntry(Traits)(string name) {}
1665 
1666 		void readArray(Traits)(scope void delegate(size_t) @safe size_callback, scope void delegate() @safe entry_callback)
1667 		{
1668 			skip("A("~unqualType!(Traits.Type).mangleof~")[");
1669 			auto bidx = result.indexOf("][");
1670 			assert(bidx > 0);
1671 			auto cnt = result[0 .. bidx].to!size_t;
1672 			result = result[bidx+2 .. $];
1673 
1674 			size_t i = 0;
1675 			while (result.startsWith("AE(")) {
1676 				result = result[3 .. $];
1677 				auto idx = result.indexOf(',');
1678 				auto idx2 = result.indexOf(")(");
1679 				assert(idx > 0 && idx2 > idx);
1680 				auto t = result[0 .. idx];
1681 				auto n = result[idx+1 .. idx2];
1682 				result = result[idx2+2 .. $];
1683 				assert(n == i.to!string);
1684 				entry_callback();
1685 				skip(")AE("~t~","~n~")");
1686 				i++;
1687 			}
1688 			skip("]A("~unqualType!(Traits.Type).mangleof~")");
1689 
1690 			assert(i == cnt);
1691 		}
1692 
1693 		void beginReadArrayEntry(Traits)(size_t index) {}
1694 		void endReadArrayEntry(Traits)(size_t index) {}
1695 
1696 		T readValue(Traits, T)()
1697 		{
1698 			skip("V("~unqualType!T.mangleof~")(");
1699 			auto idx = result.indexOf(')');
1700 			assert(idx >= 0);
1701 			auto ret = result[0 .. idx].to!T;
1702 			result = result[idx+1 .. $];
1703 			return ret;
1704 		}
1705 
1706 		void skip(string prefix)
1707 		@safe {
1708 			assert(result.startsWith(prefix), prefix ~ " vs. " ~ result);
1709 			result = result[prefix.length .. $];
1710 		}
1711 
1712 		bool tryReadNull(Traits)()
1713 		{
1714 			if (result.startsWith("null")) {
1715 				result = result[4 .. $];
1716 				return true;
1717 			} else return false;
1718 		}
1719 	}
1720 }
1721 
1722 unittest { // basic serialization behavior
1723 	import std.typecons : Nullable;
1724 
1725 	static void test(T)(auto ref T value, string expected) {
1726 		assert(serialize!TestSerializer(value) == expected, serialize!TestSerializer(value));
1727 		static if (isPointer!T) {
1728 			if (value) assert(*deserialize!(TestSerializer, T)(expected) == *value);
1729 			else assert(deserialize!(TestSerializer, T)(expected) is null);
1730 		} else static if (is(T == Nullable!U, U)) {
1731 			if (value.isNull()) assert(deserialize!(TestSerializer, T)(expected).isNull);
1732 			else assert(deserialize!(TestSerializer, T)(expected) == value);
1733 		} else assert(deserialize!(TestSerializer, T)(expected) == value);
1734 	}
1735 
1736 	test("hello", "V(Aya)(hello)");
1737 	test(12, "V(i)(12)");
1738 	test(12.0, "V(Aya)(12)");
1739 	test(12.0f, "V(f)(12)");
1740 	assert(serialize!TestSerializer(null) ==  "null");
1741 	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)");
1742 	string mangleOfAA = (string[string]).mangleof;
1743 	test(["hello": "world"], "D(" ~ mangleOfAA ~ "){DE(Aya,hello)(V(Aya)(world))DE(Aya,hello)}D(" ~ mangleOfAA ~ ")");
1744 	test(cast(int*)null, "null");
1745 	int i = 42;
1746 	test(&i, "V(i)(42)");
1747 	Nullable!int j;
1748 	test(j, "null");
1749 	j = 42;
1750 	test(j, "V(i)(42)");
1751 }
1752 
1753 unittest { // basic user defined types
1754 	static struct S { string f; }
1755 	enum Sm = S.mangleof;
1756 	auto s = S("hello");
1757 	enum s_ser = "D("~Sm~"){DE(Aya,f)(V(Aya)(hello))DE(Aya,f)}D("~Sm~")";
1758 	assert(serialize!TestSerializer(s) == s_ser, serialize!TestSerializer(s));
1759 	assert(deserialize!(TestSerializer, S)(s_ser) == s);
1760 
1761 	static class C { string f; }
1762 	enum Cm = C.mangleof;
1763 	C c;
1764 	assert(serialize!TestSerializer(c) == "null");
1765 	c = new C;
1766 	c.f = "hello";
1767 	enum c_ser = "D("~Cm~"){DE(Aya,f)(V(Aya)(hello))DE(Aya,f)}D("~Cm~")";
1768 	assert(serialize!TestSerializer(c) == c_ser);
1769 	assert(deserialize!(TestSerializer, C)(c_ser).f == c.f);
1770 
1771 	enum E { hello, world }
1772 	assert(serialize!TestSerializer(E.hello) == "V(i)(0)");
1773 	assert(serialize!TestSerializer(E.world) == "V(i)(1)");
1774 }
1775 
1776 unittest { // tuple serialization
1777 	import std.typecons : Tuple;
1778 
1779 	static struct S(T...) { T f; }
1780 	enum Sm = S!(int, string).mangleof;
1781 	enum Tum = Tuple!(int, string).mangleof;
1782 	const s = S!(int, string)(42, "hello");
1783 
1784 	const ss = serialize!TestSerializer(s);
1785 	const es = "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~")";
1786 	assert(ss == es);
1787 
1788 	const dss = deserialize!(TestSerializer, typeof(s))(ss);
1789 	assert(dss == s);
1790 
1791 	static struct T { @asArray S!(int, string) g; }
1792 	enum Tm = T.mangleof;
1793 	const t = T(s);
1794 
1795 	const st = serialize!TestSerializer(t);
1796 	const et = "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~")";
1797 	assert(st == et);
1798 
1799 	const dst = deserialize!(TestSerializer, typeof(t))(st);
1800 	assert(dst == t);
1801 }
1802 
1803 unittest { // named tuple serialization
1804 	import std.typecons : tuple;
1805 
1806 	static struct I {
1807 		int i;
1808 	}
1809 
1810 	static struct S {
1811 		int x;
1812 		string s_;
1813 	}
1814 
1815 	static struct T {
1816 		@asArray
1817 		typeof(tuple!(FieldNameTuple!I)(I.init.tupleof)) tuple1AsArray;
1818 
1819 		@name(fullyQualifiedName!I)
1820 		typeof(tuple!(FieldNameTuple!I)(I.init.tupleof)) tuple1AsDictionary;
1821 
1822 		@asArray
1823 		typeof(tuple!(FieldNameTuple!S)(S.init.tupleof)) tuple2AsArray;
1824 
1825 		@name(fullyQualifiedName!S)
1826 		typeof(tuple!(FieldNameTuple!S)(S.init.tupleof)) tuple2AsDictionary;
1827 	}
1828 
1829 	const i = I(42);
1830 	const s = S(42, "hello");
1831 	const T t = { i.tupleof, i.tupleof, s.tupleof, s.tupleof };
1832 
1833 	const st = serialize!TestSerializer(t);
1834 
1835 	enum Tm = T.mangleof;
1836 	enum TuIm = typeof(T.tuple1AsArray).mangleof;
1837 	enum TuSm = typeof(T.tuple2AsArray).mangleof;
1838 
1839 	const et =
1840 		"D("~Tm~")"~
1841 		"{"~
1842 			"DE("~TuIm~",tuple1AsArray)"~
1843 			"("~
1844 				"V(i)(42)"~
1845 			")"~
1846 			"DE("~TuIm~",tuple1AsArray)"~
1847 			"DE("~TuIm~","~fullyQualifiedName!I~")"~
1848 			"("~
1849 				"D("~TuIm~")"~
1850 				"{"~
1851 					"DE(i,i)"~
1852 					"("~
1853 						"V(i)(42)"~
1854 					")"~
1855 					"DE(i,i)"~
1856 				"}"~
1857 				"D("~TuIm~")"~
1858 			")"~
1859 			"DE("~TuIm~","~fullyQualifiedName!I~")"~
1860 			"DE("~TuSm~",tuple2AsArray)"~
1861 			"("~
1862 				"A("~TuSm~")[2]"~
1863 				"["~
1864 					"AE(i,0)"~
1865 					"("~
1866 						"V(i)(42)"~
1867 					")"~
1868 					"AE(i,0)"~
1869 					"AE(Aya,1)"~
1870 					"("~
1871 						"V(Aya)(hello)"~
1872 					")"~
1873 					"AE(Aya,1)"~
1874 				"]"~
1875 				"A("~TuSm~")"~
1876 			")"~
1877 			"DE("~TuSm~",tuple2AsArray)"~
1878 			"DE("~TuSm~","~fullyQualifiedName!S~")"~
1879 			"("~
1880 				"D("~TuSm~")"~
1881 				"{"~
1882 					"DE(i,x)"~
1883 					"("~
1884 						"V(i)(42)"~
1885 					")"~
1886 					"DE(i,x)"~
1887 					"DE(Aya,s)"~
1888 					"("~
1889 						"V(Aya)(hello)"~
1890 					")"~
1891 					"DE(Aya,s)"~
1892 				"}"~
1893 				"D("~TuSm~")"~
1894 			")"~
1895 			"DE("~TuSm~","~fullyQualifiedName!S~")"~
1896 		"}"~
1897 		"D("~Tm~")";
1898 	assert(st == et);
1899 
1900 	const dst = deserialize!(TestSerializer, typeof(t))(st);
1901 	assert(dst == t);
1902 }
1903 
1904 unittest { // testing the various UDAs
1905 	enum E { hello, world }
1906 	enum Em = E.mangleof;
1907 	static struct S {
1908 		@byName E e;
1909 		@ignore int i;
1910 		@optional float f;
1911 	}
1912 	enum Sm = S.mangleof;
1913 	auto s = S(E.world, 42, 1.0f);
1914 	assert(serialize!TestSerializer(s) ==
1915 		"D("~Sm~"){DE("~Em~",e)(V(Aya)(world))DE("~Em~",e)DE(f,f)(V(f)(1))DE(f,f)}D("~Sm~")");
1916 }
1917 
1918 unittest { // custom serialization support
1919 	// iso-ext
1920 	import std.datetime;
1921 	auto t = TimeOfDay(6, 31, 23);
1922 	assert(serialize!TestSerializer(t) == "V(Aya)(06:31:23)");
1923 	auto d = Date(1964, 1, 23);
1924 	assert(serialize!TestSerializer(d) == "V(Aya)(1964-01-23)");
1925 	auto dt = DateTime(d, t);
1926 	assert(serialize!TestSerializer(dt) == "V(Aya)(1964-01-23T06:31:23)");
1927 	auto st = SysTime(dt, UTC());
1928 	assert(serialize!TestSerializer(st) == "V(Aya)(1964-01-23T06:31:23Z)");
1929 }
1930 
1931 @safe unittest { // custom serialization support
1932 	// string
1933 	static struct S1 { int i; string toString() const @safe { return "hello"; } static S1 fromString(string) @safe { return S1.init; } }
1934 	static struct S2 { int i; string toString() const { return "hello"; } }
1935 	enum S2m = S2.mangleof;
1936 	static struct S3 { int i; static S3 fromString(string) { return S3.init; } }
1937 	enum S3m = S3.mangleof;
1938 	assert(serialize!TestSerializer(S1.init) == "V(Aya)(hello)");
1939 	assert(serialize!TestSerializer(S2.init) == "D("~S2m~"){DE(i,i)(V(i)(0))DE(i,i)}D("~S2m~")");
1940 	assert(serialize!TestSerializer(S3.init) == "D("~S3m~"){DE(i,i)(V(i)(0))DE(i,i)}D("~S3m~")");
1941 
1942 	// custom
1943 	static struct C1 { int i; float toRepresentation() const @safe { return 1.0f; } static C1 fromRepresentation(float f) @safe { return C1.init; } }
1944 	static struct C2 { int i; float toRepresentation() const { return 1.0f; } }
1945 	enum C2m = C2.mangleof;
1946 	static struct C3 { int i; static C3 fromRepresentation(float f) { return C3.init; } }
1947 	enum C3m = C3.mangleof;
1948 	assert(serialize!TestSerializer(C1.init) == "V(f)(1)");
1949 	assert(serialize!TestSerializer(C2.init) == "D("~C2m~"){DE(i,i)(V(i)(0))DE(i,i)}D("~C2m~")");
1950 	assert(serialize!TestSerializer(C3.init) == "D("~C3m~"){DE(i,i)(V(i)(0))DE(i,i)}D("~C3m~")");
1951 }
1952 
1953 unittest // Testing corner case: member function returning by ref
1954 {
1955 	import dub.internal.vibecompat.data.json;
1956 
1957 	static struct S
1958 	{
1959 		int i;
1960 		ref int foo() return { return i; }
1961 	}
1962 
1963 	static assert(__traits(compiles, { S().serializeToJson(); }));
1964 	static assert(__traits(compiles, { Json().deserializeJson!S(); }));
1965 
1966 	auto s = S(1);
1967 	assert(s.serializeToJson().deserializeJson!S() == s);
1968 }
1969 
1970 unittest // Testing corner case: Variadic template constructors and methods
1971 {
1972 	import dub.internal.vibecompat.data.json;
1973 
1974 	static struct S
1975 	{
1976 		int i;
1977 		this(Args...)(Args args) {}
1978 		int foo(Args...)(Args args) { return i; }
1979 		ref int bar(Args...)(Args args) { return i; }
1980 	}
1981 
1982 	static assert(__traits(compiles, { S().serializeToJson(); }));
1983 	static assert(__traits(compiles, { Json().deserializeJson!S(); }));
1984 
1985 	auto s = S(1);
1986 	assert(s.serializeToJson().deserializeJson!S() == s);
1987 }
1988 
1989 @safe unittest // Make sure serializing through properties still works
1990 {
1991 	import dub.internal.vibecompat.data.json;
1992 
1993 	static struct S
1994 	{
1995 		@safe:
1996 		public int i;
1997 		private int privateJ;
1998 
1999 		@property int j() @safe { return privateJ; }
2000 		@property void j(int j) @safe { privateJ = j; }
2001 	}
2002 
2003 	auto s = S(1, 2);
2004 	assert(s.serializeToJson().deserializeJson!S() == s);
2005 }
2006 
2007 @safe unittest // Immutable data deserialization
2008 {
2009 	import dub.internal.vibecompat.data.json;
2010 
2011 	static struct S {
2012 		int a;
2013 	}
2014 	static class C {
2015 		immutable(S)[] arr;
2016 	}
2017 
2018 	auto c = new C;
2019 	c.arr ~= S(10);
2020 	auto d = c.serializeToJson().deserializeJson!(immutable C);
2021 	static assert(is(typeof(d) == immutable C));
2022 	assert(d.arr == c.arr);
2023 }
2024 
2025 unittest { // test BitFlags serialization
2026 	import std.typecons : BitFlags;
2027 
2028 	enum Flag {
2029 		a = 1<<0,
2030 		b = 1<<1,
2031 		c = 1<<2
2032 	}
2033 	enum Flagm = Flag.mangleof;
2034 
2035 	alias Flags = BitFlags!Flag;
2036 	enum Flagsm = Flags.mangleof;
2037 
2038 	enum Fi_ser = "A("~Flagsm~")[0][]A("~Flagsm~")";
2039 	assert(serialize!TestSerializer(Flags.init) == Fi_ser);
2040 
2041 	enum Fac_ser = "A("~Flagsm~")[2][AE("~Flagm~",0)(V(i)(1))AE("~Flagm~",0)AE("~Flagm~",1)(V(i)(4))AE("~Flagm~",1)]A("~Flagsm~")";
2042 	assert(serialize!TestSerializer(Flags(Flag.a, Flag.c)) == Fac_ser);
2043 
2044 	struct S { @byName Flags f; }
2045 	enum Sm = S.mangleof;
2046 	enum Sac_ser = "D("~Sm~"){DE("~Flagsm~",f)(A("~Flagsm~")[2][AE("~Flagm~",0)(V(Aya)(a))AE("~Flagm~",0)AE("~Flagm~",1)(V(Aya)(c))AE("~Flagm~",1)]A("~Flagsm~"))DE("~Flagsm~",f)}D("~Sm~")";
2047 
2048 	assert(serialize!TestSerializer(S(Flags(Flag.a, Flag.c))) == Sac_ser);
2049 
2050 	assert(deserialize!(TestSerializer, Flags)(Fi_ser) == Flags.init);
2051 	assert(deserialize!(TestSerializer, Flags)(Fac_ser) == Flags(Flag.a, Flag.c));
2052 	assert(deserialize!(TestSerializer, S)(Sac_ser) == S(Flags(Flag.a, Flag.c)));
2053 }
2054 
2055 @safe unittest { // issue #1182
2056 	struct T {
2057 		int x;
2058 		string y;
2059 	}
2060 	struct S {
2061 		@asArray T t;
2062 	}
2063 
2064 	auto s = S(T(42, "foo"));
2065 	enum Sm = S.mangleof;
2066 	enum Tm = T.mangleof;
2067 	enum s_ser = "D("~Sm~"){DE("~Tm~",t)(A("~Tm~")[2][AE(i,0)(V(i)(42))AE(i,0)AE(Aya,1)(V(Aya)(foo))AE(Aya,1)]A("~Tm~"))DE("~Tm~",t)}D("~Sm~")";
2068 
2069 	auto serialized = serialize!TestSerializer(s);
2070 	assert(serialized == s_ser, serialized);
2071 	assert(deserialize!(TestSerializer, S)(serialized) == s);
2072 }
2073 
2074 @safe unittest { // issue #1352 - ingore per policy
2075 	struct P1 {}
2076 	struct P2 {}
2077 
2078 	struct T {
2079 		@ignore int a = 5;
2080 		@ignore!P1 @ignore!P2 int b = 6;
2081 		@ignore!P1 c = 7;
2082 		int d = 8;
2083 	}
2084 
2085 	auto t = T(1, 2, 3, 4);
2086 	auto Tm = T.mangleof;
2087 	auto t_ser_plain = "D("~Tm~"){DE(i,b)(V(i)(2))DE(i,b)DE(i,c)(V(i)(3))DE(i,c)DE(i,d)(V(i)(4))DE(i,d)}D("~Tm~")";
2088 	auto t_ser_p1 = "D("~Tm~"){DE(i,d)(V(i)(4))DE(i,d)}D("~Tm~")";
2089 	auto t_ser_p2 = "D("~Tm~"){DE(i,c)(V(i)(3))DE(i,c)DE(i,d)(V(i)(4))DE(i,d)}D("~Tm~")";
2090 
2091 	{
2092 		auto serialized_plain = serialize!TestSerializer(t);
2093 		assert(serialized_plain == t_ser_plain);
2094 		assert(deserialize!(TestSerializer, T)(serialized_plain) == T(5, 2, 3, 4));
2095 	}
2096 
2097 	{
2098 		auto serialized_p1 = serializeWithPolicy!(TestSerializer, P1)(t);
2099 		assert(serialized_p1 == t_ser_p1, serialized_p1);
2100 		assert(deserializeWithPolicy!(TestSerializer, P1, T)(serialized_p1) == T(5, 6, 7, 4));
2101 	}
2102 
2103 	{
2104 		auto serialized_p2 = serializeWithPolicy!(TestSerializer, P2)(t);
2105 		assert(serialized_p2 == t_ser_p2);
2106 		assert(deserializeWithPolicy!(TestSerializer, P2, T)(serialized_p2) == T(5, 6, 3, 4));
2107 	}
2108 }
2109 
2110 unittest {
2111 	import std.conv : to;
2112 	import std.string : toLower, toUpper;
2113 
2114 	template P(T) if (is(T == enum)) {
2115 		@safe:
2116 		static string toRepresentation(T v) { return v.to!string.toLower(); }
2117 		static T fromRepresentation(string str) { return str.toUpper().to!T; }
2118 	}
2119 
2120 
2121 	enum E {
2122 		RED,
2123 		GREEN
2124 	}
2125 
2126 	assert(P!E.fromRepresentation("green") == E.GREEN);
2127 	static assert(isPolicySerializable!(P, E));
2128 
2129 	auto ser_red = "V(Aya)(red)";
2130 	assert(serializeWithPolicy!(TestSerializer, P)(E.RED) == ser_red, serializeWithPolicy!(TestSerializer, P)(E.RED));
2131 	assert(deserializeWithPolicy!(TestSerializer, P, E)(ser_red) == E.RED);
2132 
2133 	import dub.internal.vibecompat.data.json : Json, JsonSerializer;
2134 	assert(serializeWithPolicy!(JsonSerializer, P)(E.RED) == Json("red"));
2135 }
2136 
2137 unittest {
2138 	static struct R { int y; }
2139 	static struct Custom {
2140 		@safe:
2141 		int x;
2142 		R toRepresentation() const { return R(x); }
2143 		static Custom fromRepresentation(R r) { return Custom(r.y); }
2144 	}
2145 
2146 	auto c = Custom(42);
2147 	auto Rn = R.mangleof;
2148 	auto ser = serialize!TestSerializer(c);
2149 	assert(ser == "D("~Rn~"){DE(i,y)(V(i)(42))DE(i,y)}D("~Rn~")");
2150 	auto deser = deserialize!(TestSerializer, Custom)(ser);
2151 	assert(deser.x == 42);
2152 }
2153 
2154 unittest {
2155 	import std.typecons : Typedef;
2156 	alias T = Typedef!int;
2157 	auto ser = serialize!TestSerializer(T(42));
2158 	assert(ser == "V(i)(42)", ser);
2159 	auto deser = deserialize!(TestSerializer, T)(ser);
2160 	assert(deser == 42);
2161 }
2162 
2163 @safe unittest {
2164 	static struct Foo { Foo[] foos; }
2165 	Foo f;
2166 	string ser = serialize!TestSerializer(f);
2167 	assert(deserialize!(TestSerializer, Foo)(ser) == f);
2168 }
2169 
2170 @system unittest {
2171 	static struct SystemSerializer {
2172 		TestSerializer ser;
2173 		alias ser this;
2174 		this(string s) { ser.result = s; }
2175 		T readValue(Traits, T)() @system { return ser.readValue!(Traits, T); }
2176 		void writeValue(Traits, T)(T value) @system { ser.writeValue!(Traits, T)(value); }
2177 		void readDictionary(Traits)(scope void delegate(string) @system entry_callback) { return ser.readDictionary!Traits((s) @trusted { entry_callback(s); }); }
2178 		void readArray(Traits)(scope void delegate(size_t) @system size_callback, scope void delegate() @system entry_callback) { ser.readArray!Traits((s) @trusted { size_callback(s); }, () @trusted { entry_callback(); }); }
2179 	}
2180 
2181 	static struct Bar { Bar[] foos; int i; }
2182 	Bar f;
2183 	string ser = serialize!SystemSerializer(f);
2184 	assert(deserialize!(SystemSerializer, Bar)(ser) == f);
2185 }
2186 
2187 @safe unittest {
2188 	static struct S { @name("+foo") int bar; }
2189 	auto Sn = S.mangleof;
2190 	auto s = S(42);
2191 	string ser = serialize!TestSerializer(s);
2192 	assert(ser == "D("~Sn~"){DE(i,+foo)(V(i)(42))DE(i,+foo)}D("~Sn~")", ser);
2193 	auto deser = deserialize!(TestSerializer, S)(ser);
2194 	assert(deser.bar == 42);
2195 }
2196 
2197 @safe unittest {
2198 	static struct S { int bar_; }
2199 	auto Sn = S.mangleof;
2200 	auto s = S(42);
2201 	string ser = serialize!TestSerializer(s);
2202 	assert(ser == "D("~Sn~"){DE(i,bar)(V(i)(42))DE(i,bar)}D("~Sn~")", ser);
2203 	auto deser = deserialize!(TestSerializer, S)(ser);
2204 	assert(deser.bar_ == 42);
2205 }
2206 
2207 @safe unittest { // issue 1941
2208 	static struct Bar { Bar[] foos; int i; }
2209 	Bar b1 = {[{null, 2}], 1};
2210 	auto s = serialize!TestSerializer(b1);
2211 	auto b = deserialize!(TestSerializer, Bar)(s);
2212 	assert(b.i == 1);
2213 	assert(b.foos.length == 1);
2214 	assert(b.foos[0].i == 2);
2215 }
2216 
2217 unittest { // issue 1991 - @system property getters/setters does not compile
2218 	static class A {
2219 		@safe:
2220 		@property @name("foo") {
2221 			string fooString() const { return "a"; }
2222 			void fooString(string a) {  }
2223 		}
2224 	}
2225 
2226 	auto a1 = new A;
2227 	auto b = serialize!TestSerializer(a1);
2228 	auto a2 = deserialize!(TestSerializer, A)(b);
2229 }
2230 
2231 unittest { // issue #2110 - single-element tuples
2232 	static struct F { int field; }
2233 
2234 	{
2235 		static struct S { typeof(F.init.tupleof) fields; }
2236 		auto b = serialize!TestSerializer(S(42));
2237 		auto a = deserialize!(TestSerializer, S)(b);
2238 		assert(a.fields[0] == 42);
2239 	}
2240 
2241 	{
2242 		static struct T { @asArray typeof(F.init.tupleof) fields; }
2243 		auto b = serialize!TestSerializer(T(42));
2244 		auto a = deserialize!(TestSerializer, T)(b);
2245 		assert(a.fields[0] == 42);
2246 	}
2247 }
2248 
2249 @safe unittest {
2250 	import std.typecons : Nullable;
2251 
2252 	struct S {
2253 		@embedNullable Nullable!int x;
2254 		@embedNullable Nullable!string s;
2255 	}
2256 
2257 	enum Sn = S.mangleof;
2258 
2259 	auto s = S(Nullable!int(3), Nullable!string.init);
2260 	auto expected = "D("~Sn~"){DE(i,x)(V(i)(3))DE(i,x)}D("~Sn~")";
2261 
2262 	assert(serialize!TestSerializer(s) == expected, serialize!TestSerializer(s));
2263 	assert(deserialize!(TestSerializer, S)(expected) == s);
2264 
2265 	s.s = "hello";
2266 	expected = "D("~Sn~"){DE(i,x)(V(i)(3))DE(i,x)DE(Aya,s)(V(Aya)(hello))DE(Aya,s)}D("~Sn~")";
2267 	assert(serialize!TestSerializer(s) == expected, serialize!TestSerializer(s));
2268 	assert(deserialize!(TestSerializer, S)(expected) == s);
2269 
2270 	s.x.nullify();
2271 	expected = "D("~Sn~"){DE(Aya,s)(V(Aya)(hello))DE(Aya,s)}D("~Sn~")";
2272 	assert(serialize!TestSerializer(s) == expected);
2273 	assert(deserialize!(TestSerializer, S)(expected) == s);
2274 
2275 	s.s.nullify();
2276 	expected = "D("~Sn~"){}D("~Sn~")";
2277 	assert(serialize!TestSerializer(s) == expected);
2278 	assert(deserialize!(TestSerializer, S)(expected) == s);
2279 }
2280 
2281 unittest {
2282 	import std.conv : ConvException;
2283 	import std.exception : assertThrown, assertNotThrown;
2284 
2285 	enum Testable : string {
2286 		foo = "foo",
2287 		bar = "bar",
2288 		baz = "bar"
2289 	}
2290 
2291 	Testable deserializeString(string value) {
2292 		return deserialize!(TestSerializer, Testable)("V(Aya)(" ~ value ~ ")");
2293 	}
2294 
2295 	foreach (string val; ["foo", "bar"])
2296 		assert(deserializeString(val) == val.to!Testable);
2297 
2298 	assertThrown!ConvException(deserializeString("foobar"));
2299 }
2300 
2301 unittest {
2302 	import std.conv : ConvException;
2303 	import std.exception : assertThrown, assertNotThrown;
2304 
2305 	enum Foo {
2306 		foobar
2307 	}
2308 
2309 	struct Testable {
2310 		@byName
2311 		Foo bar;
2312 	}
2313 
2314 	void deserializeString(string value) {
2315 		auto d = "D(" ~ Testable.mangleof ~ ")";
2316 		auto de = "DE(" ~ Foo.mangleof ~ ",bar)";
2317 		deserialize!(TestSerializer, Testable)(d ~ "{" ~ de ~ "(V(Aya)(" ~ value ~ "))" ~ de ~ "}" ~ d);
2318 	}
2319 
2320 	assertNotThrown(deserializeString("foobar"));
2321 	assertThrown!ConvException(deserializeString("baz"));
2322 }
2323 
2324 unittest {
2325 	@byName
2326 	enum Foo {
2327 		foobar
2328 	}
2329 
2330 	enum deserialized = Foo.foobar;
2331 	enum serialized = "V(Aya)(foobar)";
2332 
2333 	assert(serialize!TestSerializer(deserialized) == serialized);
2334 	assert(deserialize!(TestSerializer, Foo)(serialized) == deserialized);
2335 }
2336 
2337 unittest { // bit flag enums
2338 	enum Foo {
2339 		a = 1 << 0,
2340 		b = 1 << 1
2341 	}
2342 
2343 	const expected = "V(i)(3)";
2344 	assert(serialize!TestSerializer(Foo.a | Foo.b) == expected, serialize!TestSerializer(Foo.a | Foo.b));
2345 	assert(deserialize!(TestSerializer, Foo)(expected) == (Foo.a | Foo.b));
2346 }