1 /**
2  * The module contains serialization functions
3  *
4  * Copyright: (c) 2015-2020, Milofon Project.
5  * License: Subject to the terms of the BSD 3-Clause License, as written in the included LICENSE.md file.
6  * Author: <m.galanin@milofon.pro> Maksim Galanin
7  * Date: 2020-01-12
8  */
9 
10 module uninode.serialization;
11 
12 public import uninode.node : UniNode;
13 
14 private
15 {
16     import std.typetuple : TypeTuple;
17     import std.exception : enforce;
18     import std.conv : text, to;
19     import std.typecons;
20     import std.traits;
21     import std.meta;
22 
23     import bolts : FilterMembersOf;
24 
25     import uninode.node;
26 }
27 
28 ///Marks a method for use in serialization
29 enum SerializationMethod;
30 
31 ///Marks a method for use in deserialization
32 enum DeserializationMethod;
33 
34 
35 /**
36  * Attribute for overriding the field name during (de-)serialization.
37  */
38 NameAttribute name(string name) @safe nothrow pure @property
39 {
40     return NameAttribute(name);
41 }
42 
43 
44 /**
45  * Attribute for forcing serialization of enum fields by name instead of by value.
46  */
47 ByNameAttribyte byName() @safe nothrow pure @property
48 {
49     return ByNameAttribyte();
50 }
51 
52 
53 /**
54  * Attribute for representing a struct/class as an array instead of an object.
55  */
56 AsArrayAttribute asArray() @safe nothrow pure @property
57 {
58     return AsArrayAttribute();
59 }
60 
61 
62 /**
63  * Attribute for marking non-serialized fields.
64  */
65 IgnoreAttribute ignore() pure nothrow @safe @property
66 {
67     return IgnoreAttribute();
68 }
69 
70 
71 /**
72  * Attribute for marking non-serialized fields.
73  */
74 OptionalAttribute optional() pure nothrow @safe @property
75 {
76     return OptionalAttribute();
77 }
78 
79 
80 /**
81  * Attribute marking a field as masked during serialization.
82  */
83 MaskedAttribute masked() pure nothrow @safe @property
84 {
85     return MaskedAttribute();
86 }
87 
88 
89 /**
90  * Attribute for forcing serialization as string.
91  */
92 AsStringAttribute asString() pure nothrow @safe @property
93 {
94     return AsStringAttribute();
95 }
96 
97 
98 /**
99  * Default UniNode serializer
100  */
101 struct UniNodeSerializer {}
102 
103 
104 /**
105  * Serialize object to UniNode
106  *
107  * Params:
108  * object = serialized object
109  */
110 UniNode serializeToUniNode(T)(auto ref const T value)
111 {
112     return serialize!(UniNode, UniNodeSerializer)(value);
113 }
114 
115 
116 /**
117  * Deserialize object form UniNode
118  *
119  * Params:
120  * src = UniNode value
121  */
122 T deserializeUniNode(T)(UniNode src)
123 {
124     T value;
125     deserialize!(UniNode, UniNodeSerializer)(src, value);
126     return value;
127 }
128 
129 
130 /**
131  * Serializes a value with Serializer
132  */
133 template serialize(Node, Serializer : UniNodeSerializer)
134     if (isUniNode!Node)
135 {
136     Node serialize(T)(auto ref const T value)
137     {
138         static if (__traits(compiles, __traits(getAttributes, T)))
139         {
140             alias TA = TypeTuple!(__traits(getAttributes, T));
141             return serializeValue!(T, TA)(value);
142         }
143         else
144             return serializeValue!(T)(value);
145     }
146 
147 
148 private:
149 
150 
151     Node serializeValue(T, A...)(auto ref const T value)
152     {
153         alias TU = Unqual!T;
154 
155         static if (is(TU == typeof(null)))
156             return Node();
157         else static if (is(TU : Node))
158             return value;
159         else static if (isNullable!T)
160         {
161             if (value.isNull)
162                 return serializeValue!(A)(null);
163             else
164                 return serializeValue!(TemplateArgsOf!T[0], A)(value.get);
165         }
166         else static if (is(T == enum))
167         {
168             static if (hasAttribute!(ByNameAttribyte, A))
169                 return serializeValue!(string, A)(value.text);
170             else
171                 return serializeValue!(OriginalType!TU, A)(cast(OriginalType!TU)value);
172         }
173         else static if (isInstanceOf!(Typedef, TU))
174             return serializeValue!(TypedefType!TU, A)(cast(TypedefType!TU)value);
175         else static if (isPointer!T)
176         {
177             if (value is null)
178                 return Node();
179             return serializeValue!(PointerTarget!TU, A)(*value);
180         }
181         else static if (isTimeType!T)
182             return serializeValue!(string, A)(value.toISOExtString);
183         else static if (isSomeChar!T)
184             return serializeValue!(string, A)(value.text);
185         else static if (hasAttribute!(AsStringAttribute, A))
186             return serializeValue!(string)(value.text);
187         else static if (is(T == Tuple!TPS, TPS...))
188         {
189             import std.algorithm.searching: all;
190             enum fieldsCount = TU.Types.length;
191 
192             static if (all!"!a.empty"([TU.fieldNames]) && !hasAttribute!(AsArrayAttribute, A))
193             {
194                 Node[string] output;
195                 foreach (i, _; TU.Types)
196                 {
197                     alias TV = typeof(value[i]);
198                     enum memberName = underscoreStrip(TU.fieldNames[i]);
199                     output[memberName] = serializeValue!(TV, A)(value[i]);
200                 }
201                 return Node(output);
202             }
203             else static if (fieldsCount == 1)
204                 return serializeValue!(typeof(value[0]), A)(value[0]);
205             else
206             {
207                 Node[] output;
208                 output.reserve(fieldsCount);
209                 foreach (i, _; TU.Types)
210                 {
211                     alias TV = typeof(value[i]);
212                     output ~= serializeValue!(typeof(value[i]), A)(value[i]);
213                 }
214                 return Node(output);
215             }
216         }
217         else static if (isAssociativeArray!T)
218         {
219             alias TK = KeyType!TU;
220             alias TV = Unqual!(ValueType!TU);
221 
222             Node[string] output;
223             foreach (key, ref el; value)
224             {
225                 string keyname;
226                 static if (is(TK : string))
227                     keyname = key;
228                 else static if (is(TK : real) || is(TK : long) || is(TK == enum))
229                     keyname = key.text;
230                 else static assert(false, "Associative array keys must be strings," ~
231                         "numbers, enums.");
232                 output[keyname] = serializeValue!(TV)(el);
233             }
234             return Node(output);
235         }
236         else static if (is(TU == BitFlags!E, E))
237         {
238             size_t cnt = 0;
239             foreach (v; EnumMembers!E)
240                 if (value & v)
241                     cnt++;
242 
243             Node[] output = new Node[cnt];
244             cnt = 0;
245             foreach (v; EnumMembers!E)
246             {
247                 if (value & v)
248                     output[cnt++] = serializeValue!(E)(v);
249             }
250             return Node(output);
251         }
252         else static if (isSimpleList!T)
253         {
254             static if (isRawData!T && !hasAttribute!(AsArrayAttribute, A))
255                 return Node(value);
256             else
257             {
258                 Node[] output = new Node[value.length];
259                 alias TV = Unqual!(ForeachType!T);
260                 foreach (i, v; value)
261                     output[i] = serializeValue!(TV, A)(v);
262                 return Node(output);
263             }
264         }
265         else static if (is(TU == struct) || is(TU == class))
266         {
267             static if (is(T == class))
268                 if (value is null)
269                     return Node();
270 
271             static auto safeGetMember(string mname)(ref const T val) @safe
272             {
273                 static if (__traits(compiles, __traits(getMember, val, mname)))
274                     return __traits(getMember, val, mname);
275                 else
276                 {
277                     pragma(msg, "Warning: Getter for "~fullyQualifiedName!T~"."~mname~" is not @safe");
278                     return () @trusted { return __traits(getMember, val, mname); } ();
279                 }
280             }
281 
282             static if (hasSerializationMethod!(T, Node))
283                 return __traits(getMember, value,
284                         __traits(identifier, serializationMethod!T))();
285             else static if (hasAttribute!(AsArrayAttribute, A))
286             {
287                 alias members = FilterMembersOf!(TU, isSerializableField);
288                 enum nfields = getExpandedFieldCount!(TU, members);
289                 Node[] output = new Node[nfields];
290                 size_t fcount = 0;
291 
292                 foreach (i, mName; members)
293                 {
294                     alias TMS = TypeTuple!(typeof(__traits(getMember, value, mName)));
295                     foreach (j, TM; TMS)
296                     {
297                         alias TA = TypeTuple!(__traits(getAttributes, TypeTuple!(__traits(getMember, T, mName))[j]));
298                         static if (!isBuiltinTuple!(T, mName))
299                             output[fcount++] = serializeValue!(TM, TA)(safeGetMember!mName(value));
300                         else
301                             output[fcount++] = serializeValue!(TM, TA)(tuple(__traits(getMember, value, mName))[j]);
302                     }
303                 }
304                 return Node(output);
305             }
306             else
307             {
308                 Node[string] output;
309                 foreach (mName; FilterMembersOf!(TU, isSerializableField))
310                 {
311                     alias TM = TypeTuple!(typeof(__traits(getMember, TU, mName)));
312                     alias TA = TypeTuple!(__traits(getAttributes, TypeTuple!(__traits(getMember, T, mName))[0]));
313                     enum memberName = GetMemeberName!(T, mName);
314                     static if (!isBuiltinTuple!(T, mName))
315                         auto vt = safeGetMember!mName(value);
316                     else
317                     {
318                         alias TTM = TypeTuple!(typeof(__traits(getMember, value, mName)));
319                         auto vt = tuple!TTM(__traits(getMember, value, mName));
320                     }
321                     output[memberName] = serializeValue!(typeof(vt), TA)(vt);
322                 }
323                 return Node(output);
324             }
325         }
326         else static if (isUniNodeInnerType!TU)
327             return Node(value);
328         else
329             static assert(false, "Unsupported serialization type: " ~ T.stringof);
330     }
331 }
332 
333 
334 /**
335  * Deserializes a value with Serializer
336  */
337 template deserialize(Node, Serializer : UniNodeSerializer)
338     if (isUniNode!Node)
339 {
340     void deserialize(T)(auto ref Node value, out T result)
341     {
342         static if (__traits(compiles, __traits(getAttributes, T)))
343         {
344             alias TA = TypeTuple!(__traits(getAttributes, T));
345             result = deserializeValue!(T, TA)(value);
346         }
347         else
348             result = deserializeValue!(T)(value);
349     }
350 
351 
352 private:
353 
354 
355     T deserializeValue(T, A...)(auto ref const Node value)
356     {
357         static if (is(T == typeof(null)))
358             return typeof(null).init;
359         else static if (isNullable!T)
360         {
361             if (!value.canNil)
362                 return T(deserializeValue!(TemplateArgsOf!T[0], A)(value));
363             else
364                 return T.init;
365         }
366         else static if (isInstanceOf!(Typedef, T))
367             return T(deserializeValue!(TypedefType!T, A)(value));
368         else static if (isPointer!T)
369         {
370             if (value.canNil)
371                 return null;
372             alias PT = PointerTarget!T;
373             auto ret = new PT;
374             *ret = deserializeValue!(PT, A)(value);
375             return ret;
376         }
377         else static if (isTimeType!T)
378             return T.fromISOExtString(deserializeValue!(string, A)(value));
379         else static if (isSomeChar!T)
380         {
381             const s = deserializeValue!(string, A)(value);
382             enforceDeserialization(s.length, "String length mismatch");
383             return s[0];
384         }
385         else static if (is(T == Tuple!TPS, TPS...))
386         {
387             import std.algorithm.searching: all;
388             enum fieldsCount = T.Types.length;
389 
390             static if (all!"!a.empty"([T.fieldNames]) && !hasAttribute!(AsArrayAttribute, A))
391             {
392                 T output;
393                 bool[fieldsCount] set;
394                 foreach (ref name, ref const(Node) val; value.getMapping)
395                 {
396                     switch (name)
397                     {
398                         foreach (i, TV; T.Types)
399                         {
400                             enum fieldName = underscoreStrip(T.fieldNames[i]);
401                             case fieldName: {
402                                 output[i] = deserializeValue!(TV, A)(val);
403                                 set[i] = true;
404                             } break;
405                         }
406                         default: break;
407                     }
408                 }
409                 foreach (i, fieldName; T.fieldNames)
410                     enforceDeserialization(set[i], "Missing tuple field '"~fieldName
411                             ~"' of type '"~T.Types[i].stringof~"'.");
412                 return output;
413             }
414             else static if (fieldsCount == 1)
415                 return T(deserializeValue!(T.Types[0], A)(value));
416             else
417             {
418                 T output;
419                 size_t currentField = 0;
420                 foreach (ref const(Node) val; value.getSequence)
421                 {
422                     switch (currentField++)
423                     {
424                         foreach (i, TV; T.Types)
425                         {
426                             case i:
427                                 output[i] = deserializeValue!(TV, A)(val);
428                                 break;
429                         }
430                         default: break;
431                     }
432                 }
433                 enforceDeserialization(currentField == fieldsCount,
434                         "Missing tuple field(s) - expected '"~fieldsCount.stringof
435                             ~"', received '"~currentField.stringof~"'.");
436                 return output;
437             }
438         }
439         else static if (is(T == BitFlags!E, E))
440         {
441             T output;
442             foreach (ref idx, ref const(Node) val; value.getSequence)
443                 output |= deserializeValue!(E, A)(val);
444             return output;
445         }
446         else static if (is(T == enum))
447         {
448             static if (hasAttribute!(ByNameAttribyte, A))
449                 return deserializeValue!(string, A)(value).to!T;
450             else
451                 return cast(T)deserializeValue!(OriginalType!T, A)(value);
452         }
453         else static if (isStaticArray!T)
454         {
455             alias TV = typeof(T.init[0]);
456             T output;
457             enforceDeserialization(value.length == T.length, "Static array length mismatch");
458 
459             if (value.canRaw)
460             {
461                 foreach (ref idx, val; value.get!Bytes)
462                     output[idx] = val;
463             }
464             else
465             {
466                 foreach (ref idx, ref const(Node) val; value.getSequence)
467                     output[idx] = deserializeValue!(TV)(val);
468             }
469 
470             return output;
471         }
472         else static if (isSimpleList!T)
473         {
474             alias TV = typeof(T.init[0]);
475 
476             T output;
477             output.reserve(value.length);
478 
479             if (value.canRaw)
480             {
481                 static if (isNumeric!(ForeachType!T))
482                 {
483                     foreach (val; value.get!Bytes)
484                         output ~= val;
485                 }
486 
487                 return output;
488             }
489             else
490             {
491                 foreach (ref idx, ref const(Node) val; value.getSequence)
492                     output ~= deserializeValue!(TV)(val);
493             }
494 
495             return output;
496         }
497         else static if (isAssociativeArray!T)
498         {
499             alias TK = KeyType!T;
500             alias TV = ValueType!T;
501             T output;
502 
503             foreach (ref name, ref const(Node) val; value.getMapping)
504             {
505                 TK key;
506                 static if (is(TK == string) || (is(TK == enum)
507                             && is(OriginalType!TK == string)))
508                     key = cast(TK)name;
509                 else static if (is(TK : real) || is(TK : long) || is(TK == enum))
510                     key = name.to!TK;
511                 else
512                     static assert(false, "Associative array keys must be strings," ~
513                         "numbers, enums.");
514                 output[key] = deserializeValue!(TV, A)(val);
515             }
516 
517             return output;
518         }
519         else static if (is(T : Node))
520             return value;
521         else static if (is(T == struct) || is(T == class))
522         {
523             static if (is(T == class))
524                 if (value.canNil)
525                     return null;
526 
527             void safeSetMember(string mname, U)(ref T value, U fval) @safe
528             {
529                 static if (__traits(compiles, () @safe { __traits(getMember, value, mname) = fval; }))
530                     __traits(getMember, value, mname) = fval;
531                 else
532                 {
533                     pragma(msg, "Warning: Setter for "~fullyQualifiedName!T~"."~mname~" is not @safe");
534                     () @trusted { __traits(getMember, value, mname) = fval; } ();
535                 }
536             }
537 
538             bool canDeserializable(A...)(Node val)
539             {
540                 static if (hasAttribute!(OptionalAttribute, A))
541                     return !val.canNil();
542                 else
543                     return true;
544             }
545 
546             static if (hasDeserializationMethod!(T, Node))
547                 return deserializationMethod!T(value);
548             else
549             {
550                 T output;
551                 static if (is(T == class))
552                     output = new T;
553                 alias Members = FilterMembersOf!(T, isDeserializableField);
554                 enum FDS = getExpandedFieldsData!(T, Members);
555                 bool[FDS.length] set;
556 
557                 static if (hasAttribute!(AsArrayAttribute, A))
558                 {
559                     foreach (ref idx, ref const(Node) val; value.getSequence)
560                     {
561                         switch (idx)
562                         {
563                             foreach (i, FD; FDS)
564                             {
565                                 enum mName = FD[0];
566                                 enum mIndex = FD[1];
567                                 alias MT = TypeTuple!(__traits(getMember, T, mName));
568                                 alias MTI = MT[mIndex];
569                                 alias TMTI = typeof(MTI);
570                                 alias TMTIA = TypeTuple!(__traits(getAttributes, MTI));
571                             case i:
572                                 if (canDeserializable!TMTIA(val))
573                                 {
574                                     static if (!isBuiltinTuple!(T, mName))
575                                         safeSetMember!mName(output, deserializeValue!(TMTI, TMTIA)(val));
576                                     else
577                                         __traits(getMember, output, mName)[mIndex] = deserializeValue!(TMTI,
578                                                 TMTIA)(val);
579                                 }
580                                 set[i] = true;
581                                 break;
582                             }
583                             default: break;
584                         }
585                     }
586                 }
587                 else
588                 {
589                     foreach (ref name, ref const(Node) val; value.getMapping)
590                     {
591                         switch (name)
592                         {
593                             foreach (i, mName; Members)
594                             {
595                                 alias TM = TypeTuple!(typeof(__traits(getMember, T, mName)));
596                                 alias TA = TypeTuple!(__traits(getAttributes, TypeTuple!(__traits(getMember, T,
597                                                     mName))[0]));
598                                 enum memberName = GetMemeberName!(T, mName);
599                             case memberName:
600                                 if (canDeserializable!TA(val))
601                                 {
602                                     static if (!isBuiltinTuple!(T, mName))
603                                         safeSetMember!mName(output, deserializeValue!(TM, TA)(val));
604                                     else
605                                         __traits(getMember, output, mName) = deserializeValue!(Tuple!TM, TA)(val);
606                                 }
607                                 set[i] = true;
608                                 break;
609                             }
610                             default: break;
611                         }
612                     }
613                 }
614 
615                 foreach (i, mName; Members)
616                 {
617                     alias MTA = __traits(getAttributes, __traits(getMember, T, mName));
618                     static if (!hasAttribute!(OptionalAttribute, MTA))
619                         enforceDeserialization(set[i], "Missing non-optional field '"~mName
620                                 ~"' of type '"~T.stringof~"'.");
621                 }
622 
623                 return output;
624             }
625         }
626         else static if (isUniNodeInnerType!T)
627         {
628             static if (is(T == Node))
629                 return value;
630             else
631                 return value.get!T;
632         }
633         else
634             static assert(false, "Unsupported serialization type: " ~ T.stringof);
635     }
636 }
637 
638 
639 /**
640  * Thrown on UniNode deserialization errors
641  */
642 class UniNodeDeserializationException : Exception
643 {
644     /**
645      * common constructor
646      */
647     pure @safe @nogc nothrow
648     this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null)
649     {
650         super(msg, file, line, next);
651     }
652 }
653 
654 
655 package alias enforceDeserialization = enforce!UniNodeDeserializationException;
656 
657 
658 private:
659 
660 
661 /**
662  * Check nullable Type
663  */
664 enum isNullable(T) = isInstanceOf!(Nullable, T);
665 
666 @("Should work isNullable")
667 @safe unittest
668 {
669     assert (isNullable!(Nullable!int));
670     assert (isNullable!(Nullable!(int, 0)));
671     assert (!isNullable!int);
672 }
673 
674 
675 /**
676  * Check datetime type
677  */
678 template isTimeType(T) {
679     import std.datetime : DateTime, Date, SysTime, TimeOfDay;
680     enum isTimeType = is(T == SysTime) || is(T == DateTime) || is(T == Date)
681         || is(T == TimeOfDay);
682 }
683 
684 @("Should work isTimeType")
685 @safe unittest
686 {
687     import std.datetime : DateTime, Date, SysTime, TimeOfDay;
688     assert (isTimeType!DateTime);
689     assert (isTimeType!Date);
690     assert (isTimeType!SysTime);
691     assert (isTimeType!TimeOfDay);
692 }
693 
694 
695 /**
696  * Determines if a member is a public, non-static, de-facto data field.
697  * In addition to plain data fields, R/W properties are also accepted.
698  */
699 template isSerializableAvailableField(T, string M)
700 {
701     import std.typetuple : TypeTuple;
702 
703     static void testAssign()()
704     {
705         T t = void;
706         __traits(getMember, t, M) = __traits(getMember, t, M);
707     }
708 
709     // reject type aliases
710     static if (is(TypeTuple!(__traits(getMember, T, M))))
711         enum isSerializableAvailableField = false;
712     // reject non-public members
713     else static if (!isPublicMember!(T, M))
714         enum isSerializableAvailableField = false;
715     // reject static members
716     else static if (!isNonStaticMember!(T, M))
717         enum isSerializableAvailableField = false;
718     // reject non-typed members
719     else static if (!is(typeof(__traits(getMember, T, M))))
720         enum isSerializableAvailableField = false;
721     // reject void typed members (includes templates)
722     else static if (is(typeof(__traits(getMember, T, M)) == void))
723         enum isSerializableAvailableField = false;
724     // reject non-assignable members
725     else static if (!__traits(compiles, testAssign!()()))
726         enum isSerializableAvailableField = false;
727     // reject ignore members
728     else static if (anySatisfy!(isSomeFunction, __traits(getMember, T, M)))
729     {
730         // If M is a function, reject if not @property or returns by ref
731         private enum FA = functionAttributes!(__traits(getMember, T, M));
732         enum isSerializableAvailableField = (FA & FunctionAttribute.property) != 0;
733     }
734     else
735         enum isSerializableAvailableField = true;
736 }
737 
738 
739 /**
740  * Determins if a member is a public, non-static data field.
741  */
742 template isSerializablePlainField(T, string M)
743 {
744     private T tGen(){ return T.init; }
745 
746     static if (!isSerializableField!(T, M))
747         enum isSerializablePlainField = false;
748     else
749     {
750         enum isSerializablePlainField = __traits(compiles,
751                 *(&__traits(getMember, tGen(), M)) = *(&__traits(getMember, tGen(), M)));
752     }
753 }
754 
755 
756 /**
757  * Determins if a member is serializable
758  */
759 template isSerializableField(T, string M)
760 {
761     static if (isSerializableAvailableField!(T, M))
762     {
763         alias AA = __traits(getAttributes, __traits(getMember, T, M));
764         static if (hasAttribute!(MaskedAttribute, AA))
765             enum isSerializableField = false;
766         else static if (hasAttribute!(IgnoreAttribute, AA))
767             enum isSerializableField = false;
768         else
769             enum isSerializableField = true;
770     }
771     else
772         enum isSerializableField = false;
773 }
774 
775 
776 /**
777  * Determins if a member is deseriazable
778  */
779 template isDeserializableField(T, string M)
780 {
781     static if (isSerializableAvailableField!(T, M))
782     {
783         alias AA = __traits(getAttributes, __traits(getMember, T, M));
784         static if (hasAttribute!(IgnoreAttribute, AA))
785             enum isDeserializableField = false;
786         else static if (hasAttribute!(AsStringAttribute, AA))
787             enum isDeserializableField = false;
788         else
789             enum isDeserializableField = true;
790     }
791     else
792         enum isDeserializableField = false;
793 }
794 
795 
796 @("Should work isSerializableAvailableField")
797 @safe unittest
798 {
799     import std.algorithm.searching : canFind;
800 
801     struct S
802     {
803         alias a = int;        // alias
804         int i;                // plain field
805         enum j = 42;          // manifest constant
806         static int k = 42;    // static field
807         private int privateJ; //private field
808 
809         this(Args...)(Args args) {}
810 
811         // read-write property (OK)
812         @property int p1() { return privateJ; }
813         @property void p1(int j) { privateJ = j; }
814         // read-only property (NO)
815         @property int p2() { return privateJ; }
816         // write-only property (NO)
817         @property void p3(int value) { privateJ = value; }
818         // ref returning property (OK)
819         @property ref int p4() { return i; }
820         // parameter-less template property (OK)
821         @property ref int p5()() { return i; }
822         // not treated as a property by DMD, so not a field
823         @property int p6()() { return privateJ; }
824         @property void p6(int j)() { privateJ = j; }
825 
826         static @property int p7() { return k; }
827         static @property void p7(int value) { k = value; }
828 
829         ref int f1() { return i; } // ref returning function (no field)
830 
831         int f2(Args...)(Args) { return i; }
832 
833         ref int f3(Args...)(Args) { return i; }
834 
835         void someMethod() {}
836 
837         ref int someTempl()() { return i; }
838     }
839 
840     static immutable plainFields = ["i"];
841     static immutable fields = ["i", "p1", "p4", "p5"];
842 
843     foreach (fName; __traits(allMembers, S))
844     {
845         static if (isSerializableField!(S, fName))
846             assert (fields.canFind(fName), fName ~ " detected as field.");
847         else
848             assert (!fields.canFind(fName), fName ~ " not detected as field.");
849 
850         static if (isSerializablePlainField!(S, fName))
851             assert (plainFields.canFind(fName), fName ~ " not detected as plain field.");
852         else
853             assert (!plainFields.canFind(fName), fName ~ " not detected as plain field.");
854     }
855 }
856 
857 
858 /**
859  * Tests if a member requires $(D this) to be used.
860  */
861 template isNonStaticMember(T, string M)
862 {
863     import std.typetuple;
864     import std.traits;
865 
866     static if (!__traits(compiles, TypeTuple!(__traits(getMember, T, M))))
867         enum isNonStaticMember = false;
868     else
869     {
870         alias MF = TypeTuple!(__traits(getMember, T, M));
871         static if (M.length == 0)
872             enum isNonStaticMember = false;
873         else static if (anySatisfy!(isSomeFunction, MF))
874             enum isNonStaticMember = !__traits(isStaticFunction, MF);
875         else
876             enum isNonStaticMember = !__traits(compiles, (){ auto x = __traits(getMember, T, M); }());
877     }
878 }
879 
880 @("Should work isNonStaticMember template")
881 @safe unittest
882 {
883     struct S
884     {
885         int a;
886         static int b;
887         enum c = 42;
888         void f();
889         static void g();
890         ref int h() { return a; }
891         static ref int i() { return b; }
892     }
893 
894     assert (isNonStaticMember!(S, "a"));
895     assert (!isNonStaticMember!(S, "b"));
896     assert (!isNonStaticMember!(S, "c"));
897     assert (isNonStaticMember!(S, "f"));
898     assert (!isNonStaticMember!(S, "g"));
899     assert (isNonStaticMember!(S, "h"));
900     assert (!isNonStaticMember!(S, "i"));
901 }
902 
903 @("Should work isNonStaticMember tuple fields")
904 @safe unittest
905 {
906     struct S(T...)
907     {
908         T a;
909         static T b;
910     }
911 
912     alias T = S!(int, float);
913     assert (isNonStaticMember!(T, "a"));
914     assert (!isNonStaticMember!(T, "b"));
915 
916     alias U = S!();
917     assert (!isNonStaticMember!(U, "a"));
918     assert (!isNonStaticMember!(U, "b"));
919 }
920 
921 
922 /**
923  * Tests if the protection of a member is public.
924  */
925 template isPublicMember(T, string M)
926 {
927     import std.algorithm : among;
928 
929     static if (!__traits(compiles, TypeTuple!(__traits(getMember, T, M))))
930         enum isPublicMember = false;
931     else
932     {
933         alias MEM = TypeTuple!(__traits(getMember, T, M));
934         enum isPublicMember = __traits(getProtection, MEM).among("public", "export");
935     }
936 }
937 
938 @("Should work isPublicMember")
939 @safe unittest
940 {
941     class C
942     {
943         int a;
944         export int b;
945         protected int c;
946         private int d;
947         package int e;
948         void f() {}
949         static void g() {}
950         private void h() {}
951         private static void i() {}
952     }
953 
954     assert (isPublicMember!(C, "a"));
955     assert (isPublicMember!(C, "b"));
956     assert (!isPublicMember!(C, "c"));
957     assert (!isPublicMember!(C, "d"));
958     assert (!isPublicMember!(C, "e"));
959     assert (isPublicMember!(C, "f"));
960     assert (isPublicMember!(C, "g"));
961     assert (!isPublicMember!(C, "h"));
962     assert (!isPublicMember!(C, "i"));
963 
964     struct S
965     {
966         int a;
967         export int b;
968         private int d;
969         package int e;
970     }
971     assert (isPublicMember!(S, "a"));
972     assert (isPublicMember!(S, "b"));
973     assert (!isPublicMember!(S, "d"));
974     assert (!isPublicMember!(S, "e"));
975 
976     S s;
977     s.a = 21;
978     assert (s.a == 21);
979 }
980 
981 
982 /**
983  * Check Tuple
984  */
985 template isBuiltinTuple(T, string member)
986 {
987     alias TM = AliasSeq!(typeof(__traits(getMember, T.init, member)));
988     static if (TM.length > 1) enum isBuiltinTuple = true;
989     else static if (is(typeof(__traits(getMember, T.init, member)) == TM[0]))
990         enum isBuiltinTuple = false;
991     else enum isBuiltinTuple = true; // single-element tuple
992 }
993 
994 
995 /**
996  * Get expanded fields count
997  */
998 size_t getExpandedFieldCount(T, FIELDS...)()
999 {
1000     size_t ret = 0;
1001     foreach (F; FIELDS)
1002         ret += TypeTuple!(__traits(getMember, T, F)).length;
1003     return ret;
1004 }
1005 
1006 
1007 /**
1008  * Get expanded fields data
1009  */
1010 template getExpandedFieldsData(T, FIELDS...)
1011 {
1012     import std.meta : aliasSeqOf, staticMap;
1013     import std.range : repeat, zip, iota;
1014 
1015     enum subfieldsCount(alias F) = TypeTuple!(__traits(getMember, T, F)).length;
1016     alias processSubfield(alias F) = aliasSeqOf!(zip(repeat(F), iota(subfieldsCount!F)));
1017     alias getExpandedFieldsData = staticMap!(processSubfield, FIELDS);
1018 }
1019 
1020 
1021 /**
1022  * Strip underscore
1023  */
1024 string underscoreStrip(string fieldName) @safe nothrow @nogc
1025 {
1026     if (fieldName.length < 1 || fieldName[$-1] != '_')
1027         return fieldName;
1028     else
1029         return fieldName[0 .. $-1];
1030 }
1031 
1032 
1033 /**
1034  * Return member name
1035  */
1036 template GetMemeberName(T, string M)
1037 {
1038     alias isNamed(alias M) = hasUDA!(M, NameAttribute);
1039     alias FM = Filter!(isNamed, TypeTuple!(__traits(getMember, T, M)));
1040     static if (FM.length > 0)
1041         enum GetMemeberName = underscoreStrip(getUDAs!(FM[0], NameAttribute)[0].name);
1042     else
1043         enum GetMemeberName = underscoreStrip(M);
1044 }
1045 
1046 
1047 /**
1048  * Check attribute
1049  */
1050 template hasAttribute(alias T, ATTRIBUTES...)
1051 {
1052     static if (ATTRIBUTES.length == 1)
1053         enum hasAttribute = is(typeof(ATTRIBUTES[0]) == T);
1054     else static if (ATTRIBUTES.length > 1)
1055         enum hasAttribute = hasAttribute!(T, ATTRIBUTES[0 .. $/2]) || hasAttribute!(T, ATTRIBUTES[$/2 .. $]);
1056     else
1057         enum hasAttribute = false;
1058 }
1059 
1060 
1061 /**
1062  * Return serialization method
1063  */
1064 alias serializationMethod(T) = getSymbolsByUDA!(T, SerializationMethod)[0];
1065 
1066 
1067 /**
1068  * Check SerializationMethod
1069  */
1070 template hasSerializationMethod(T, Node)
1071 {
1072     alias methods = getSymbolsByUDA!(T, SerializationMethod);
1073 
1074     static if (methods.length == 1)
1075         enum hasSerializationMethod = Parameters!(methods[0]).length == 0
1076                 && is(ReturnType!(methods[0]) : Node);
1077     else
1078         enum hasSerializationMethod = false;
1079 }
1080 
1081 
1082 /**
1083  * Return deserialization method
1084  */
1085 alias deserializationMethod(T) = getSymbolsByUDA!(T, DeserializationMethod)[0];
1086 
1087 
1088 /**
1089  * Check SerializationMethod
1090  */
1091 template hasDeserializationMethod(T, Node)
1092 {
1093     alias methods = getSymbolsByUDA!(T, DeserializationMethod);
1094 
1095     static if (methods.length == 1)
1096         enum hasDeserializationMethod = Parameters!(methods[0]).length == 1
1097                 && is(Parameters!(methods[0])[0] : Node)
1098                 && is(ReturnType!(methods[0]) == T);
1099     else
1100         enum hasDeserializationMethod = false;
1101 }
1102 
1103 
1104 /**
1105  * Check type of simple list
1106  */
1107 alias isSimpleList = templateAnd!(isArray, templateNot!isSomeString,
1108         templateNot!isAssociativeArray);
1109 
1110 @("Should work isSimpleList")
1111 @safe unittest
1112 {
1113     assert(isSimpleList!(int[]));
1114     assert(isSimpleList!(string[]));
1115     assert(!isSimpleList!(string));
1116     assert(!isSimpleList!(char[]));
1117     assert(!isSimpleList!(int));
1118     assert(!isSimpleList!(int[string]));
1119     assert(isSimpleList!(char[10]));
1120 }
1121 
1122 
1123 /**
1124  * Attribute for overriding the field name during (de-)serialization.
1125  */
1126 struct NameAttribute
1127 {
1128     /// Custom member name
1129     string name;
1130 }
1131 
1132 
1133 /**
1134  * Attribute for forcing serialization of enum fields by name instead of by value.
1135  */
1136 struct ByNameAttribyte {}
1137 
1138 
1139 /**
1140  * Attribute for representing a struct/class as an array instead of an object.
1141  */
1142 struct AsArrayAttribute {}
1143 
1144 
1145 /**
1146  * Attribute marking a field as optional during deserialization.
1147  */
1148 struct OptionalAttribute {}
1149 
1150 
1151 /**
1152  * Attribute marking a field as masked during serialization.
1153  */
1154 struct MaskedAttribute {}
1155 
1156 
1157 /**
1158  *	Attribute for marking non-serialized fields.
1159  */
1160 struct IgnoreAttribute {}
1161 
1162 
1163 /**
1164  * Attribute for forcing serialization as string.
1165  */
1166 struct AsStringAttribute {}
1167