1 module yamlserialized.deserialization;
2 
3 import std.conv;
4 import std.traits;
5 
6 import yamlserialized.types;
7 
8 import dyaml;
9 
10 /// Deserialize a D-YAML Node into an array T
11 void deserializeInto(T)(Node yamlNode, ref T array) if (isArray!T) {
12     alias ElementType = ForeachType!T;
13 
14     // Iterate each item in the array of nodes and add them to values, converting them to the actual type
15     foreach(item; yamlNode.as!(Node[])) {
16         static if (is(ElementType == struct)) {
17             // This item is a struct - instantiate it
18             ElementType newStruct;
19 
20             // ...deserialize into the new instance
21             item.deserializeInto(newStruct);
22 
23             // ...and add it to the array
24             array ~= newStruct;
25         }
26         else static if (is(ElementType == class)) {
27             // The item type is class - create a new instance
28             auto newClass = new ElementType();
29 
30             // ...deserialize into the new instance
31             item.deserializeInto(newClass);
32 
33             // ...and add it to the array
34             array ~= newClass;
35         }
36         else static if (isSomeString!ElementType) {
37             array ~= item.as!string.to!ElementType;
38         }
39         else static if (isArray!ElementType) {
40             // An array of arrays. Recursion time!
41             ElementType subArray;
42 
43             item.deserializeInto(subArray);
44             array ~= subArray;
45         }
46         else {
47             array ~= item.as!ElementType;
48         }
49     }
50 }
51 
52 /// Deserialize a D-YAML Node into an associative array T
53 void deserializeInto(T)(Node yamlNode, ref T associativeArray) if (isAssociativeArray!T) {
54     alias VType = ValueType!T;
55 
56     // Iterate each Pair in the Node
57     foreach(pair; yamlNode.as!(Node.Pair[])) {
58         auto key = pair.key.as!string.to!(KeyType!T);
59         auto value = pair.value;
60 
61         static if (isAssociativeArray!VType) {
62             /* The associative array's value type is another associative array type.
63                It's recursion time. */
64 
65             if (key in associativeArray) {
66                 value.deserializeInto(associativeArray[key]);
67             }
68             else {
69                 VType subAssocArray;
70 
71                 value.deserializeInto(subAssocArray);
72                 associativeArray[key] = subAssocArray;
73             }
74         }
75         else static if (is(VType == struct)) {
76             // The value type is a struct - instantiate it
77             VType newStruct;
78 
79             // ...deserialize into the new instance
80             value.deserializeInto(newStruct);
81 
82             // ...and add it to the associative array
83             associativeArray[key] = newStruct;
84         }
85         else static if (is(VType == class)) {
86             // The value type is class - create a new instance
87             auto newClass = new VType();
88 
89             // ...deserialize into the new instance
90             value.deserializeInto(newClass);
91 
92             // ...and add it to the associative array
93             associativeArray[key] = newClass;
94         }
95         else {
96             associativeArray[key] = value.as!VType;
97         }
98     }
99 }
100 
101 /// Deserialize a D-YAML Node into a struct or class of type T
102 void deserializeInto(T)(Node yamlNode, ref T obj) if (is(T == struct) || is(T == class)) {
103     enum fieldNames = FieldNameTuple!T;
104 
105     foreach(fieldName; fieldNames) {
106         alias FieldType = typeof(__traits(getMember, obj, fieldName));
107 
108         auto yamlFieldName = fieldName;
109 
110         static if (hasUDA!(__traits(getMember, obj, fieldName), YamlField)) {
111             yamlFieldName = getUDAs!(__traits(getMember, obj, fieldName), YamlField)[0].name;
112         }
113 
114         if (!yamlNode.containsKey(yamlFieldName)) {
115             continue;
116         }
117 
118         static if (is(FieldType == struct)) {
119             // This field is a struct - recurse into it
120             yamlNode[yamlFieldName].deserializeInto(__traits(getMember, obj, fieldName));
121         }
122         else static if (is(FieldType == class)) {
123             // This field is a class - recurse into it unless it is null
124             if (__traits(getMember, obj, fieldName) !is null) {
125                 yamlNode[yamlFieldName].deserializeInto(__traits(getMember, obj, fieldName));
126             }
127         }
128         else static if (isSomeChar!FieldType) {
129             // Field is a char
130             // Node.as!char fails for some reason, so we have to retrieve it as a string first
131             // and then convert it to the correct type.
132             __traits(getMember, obj, fieldName) = yamlNode[yamlFieldName].as!string.to!FieldType;
133         }
134         else static if (isSomeString!FieldType) {
135             // Field is a string
136             __traits(getMember, obj, fieldName) = yamlNode[yamlFieldName].as!string.to!FieldType;
137         }
138         else static if (isArray!FieldType) {
139             // Field is an array
140             yamlNode[yamlFieldName].deserializeInto(__traits(getMember, obj, fieldName));
141         }
142         else static if (isAssociativeArray!FieldType) {
143             // Field is an associative array
144             yamlNode[yamlFieldName].deserializeInto(__traits(getMember, obj, fieldName));
145         }
146         else static if (isIntegral!FieldType) {
147             // Field is an integer
148             if (yamlNode[yamlFieldName].convertsTo!FieldType) {
149                 // If node contains an integer value, get it directly
150                 __traits(getMember, obj, fieldName) = yamlNode[yamlFieldName].as!FieldType;
151             }
152             else {
153                 // If node contains a non-integer value, convert it to a string first and then to the correct type
154                 __traits(getMember, obj, fieldName) = yamlNode[yamlFieldName].as!string.to!FieldType;
155             }
156         }
157         else static if (isBoolean!FieldType) {
158             // Convert to string first, then to the correct boolean type.
159             __traits(getMember, obj, fieldName) = yamlNode[yamlFieldName].as!string.to!FieldType;
160         }
161         else {
162             __traits(getMember, obj, fieldName) = yamlNode[yamlFieldName].as!FieldType;
163         }
164     }
165 }
166 
167 /// Deserialize a D-YAML Node into a struct of type T
168 T deserializeTo(T)(Node yamlNode) if (is(T == struct)) {
169     T obj;
170 
171     yamlNode.deserializeInto(obj);
172     return obj;
173 }