1 module keyvalues.deserializer;
2
3 import std.algorithm;
4 import std.conv;
5 import std.range;
6 import std.string;
7 import std.traits;
8
9 import keyvalues.keyvalue;
10 import keyvalues.parser;
11
12 private enum decodable(Layout) = isScalarType!Layout || isSomeString!Layout;
13 private enum deserializable(Layout) = is(Layout == struct) && __traits(isPOD, Layout);
14
15 /++
16 Attribute for a field of a Layout which may be missing.
17 +/
18 struct Optional {}
19
20 /++
21 Deserialize a KeyValues object into the given Layout.
22
23 A Layout is a struct defining the structure of the KeyValues object.
24 A Layout must be a plain old struct (no fancy constructors),
25 contain only basic types, similarly restricted structs,
26 or arrays of the aforementioned types.
27
28 Params:
29 root = the object to deserialize
30 path = for internal use only, used to report the path to missing keys
31 +/
32 Layout deserializeKeyValues(Layout)(KeyValue root, string path = "root")
33 {
34 static assert(deserializable!Layout, "Cannot deserialize to " ~ Layout.stringof);
35
36 Layout result;
37
38 foreach(fieldName; __traits(allMembers, Layout))
39 {
40 enum getMember = "__traits(getMember, Layout, fieldName)";
41 alias FieldType = typeof(mixin(getMember));
42
43 static if(isCallable!FieldType)
44 continue;
45 else
46 {
47 string serializedName = fieldName //docs use PascalCase for keys
48 .take(1)
49 .map!toUpper
50 .chain(fieldName.drop(1))
51 .to!string
52 ;
53 string subpath = path ~ "." ~ fieldName;
54 auto subkeys = root[serializedName];
55 bool required = !hasUDA!(mixin(getMember), Optional);
56
57 if(subkeys.empty)
58 {
59 if(required)
60 throw new Exception("Required key %s not found".format(subpath));
61
62 continue;
63 }
64
65 static if(is(FieldType == struct))
66 mixin("result." ~ fieldName) = subkeys
67 .front
68 .deserializeKeyValues!FieldType(subpath)
69 ;
70 else static if(decodable!FieldType)
71 mixin("result." ~ fieldName) = subkeys
72 .front
73 .value
74 .to!FieldType
75 ;
76 else static if(isDynamicArray!FieldType)
77 {
78 alias FieldElementType = ElementType!FieldType;
79
80 static if(is(FieldElementType == struct))
81 {
82 string subkeysName = FieldElementType.stringof;
83 auto subkeysPath = subpath ~ "." ~ subkeysName;
84
85 mixin("result." ~ fieldName) = subkeys
86 .front[subkeysName]
87 .map!(kv => kv.deserializeKeyValues!FieldElementType(subkeysPath))
88 .array
89 ;
90 }
91 else if(decodable!FieldElementType)
92 mixin("result." ~ fieldName) = subkeys.front
93 .map!(kv => kv.value.to!FieldElementType)
94 .array
95 ;
96 else
97 static assert(false, "Can't deserialize array of " ~ FieldElementType);
98 }
99 else
100 static assert(false, "Can't deserialize " ~ FieldType.stringof);
101 }
102 }
103
104 return result;
105 }
106
107 unittest
108 {
109 struct Test
110 {
111 int abc;
112 string def;
113 }
114
115 auto parsed = q{
116 Abc 32
117 Def "abc def"
118 }.parseKeyValues;
119
120 assert(parsed.deserializeKeyValues!Test == Test(32, "abc def"));
121 }
122
123 unittest
124 {
125 struct Repeated
126 {
127 int abc;
128 }
129
130 struct Test
131 {
132 Repeated[] repeats;
133 string def;
134 }
135
136 auto parsed = q{
137 Repeats
138 {
139 Repeated
140 {
141 Abc 1
142 }
143 Repeated
144 {
145 Abc 2
146 }
147 }
148 Def "abc def"
149 }.parseKeyValues;
150
151 assert(parsed.deserializeKeyValues!Test == Test([Repeated(1), Repeated(2)], "abc def"));
152 }