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 }