1 module vest.json.tojson;
2 
3 import std.json      : JSONValue;
4 import std.array     : array, empty;
5 import std.algorithm : map, filter;
6 import std.traits    : isType, isFunction, isArray, isAssociativeArray, isIterable, isPointer, isSomeChar, isSomeString;
7 import std.traits    : Unqual, OriginalType;
8 import std.conv      : to;
9 import std.typecons  : isTuple;
10 import std.meta      : Alias;
11 import std.typecons  : tuple;
12 
13 // Is field property of T
14 private
15 template isProperty(T, string field)
16 {
17     alias fieldValue = Alias!(__traits(getMember, T, field));
18     enum  isProperty = !isType!(fieldValue) && !isFunction!(fieldValue) && !is(typeof(fieldValue) == void);
19 }
20 
21 
22 // Check if field is accessible
23 private
24 enum isAccessible(T, string field) = __traits(compiles, __traits(getMember, T, field));
25 
26 private
27 enum isPublic(T, string field) = ("public" == __traits(getProtection, mixin("T." ~ field)) );
28 
29 // Helper function to retrive all structure properties
30 private
31 template _retriveProperties(T, string[] fields)
32 {
33     static if(!fields.length) {
34         enum string[] _retriveProperties = [];
35     } else {
36         static if( isAccessible!(T, fields[0]) && isProperty!(T, fields[0])  ) {
37             enum _retriveProperties = [fields[0]] ~ _retriveProperties!(T, fields[1..$]);
38         } else {
39             enum _retriveProperties = _retriveProperties!(T, fields[1..$]);
40         }
41     }
42 }
43 
44 // All structure properties (not members)
45 private
46 enum allProperties(T) = _retriveProperties!(T, [__traits(allMembers, T)]);
47 
48 // Retrive associative tuple fields
49 private
50 string[] expandFieldNames(Names...)( Names names)
51 {
52     static if( Names.length ) {
53         return names[0] ~ expandFieldNames(names[1..$]);
54     } else {
55         return [];
56     }
57 }
58 
59 // Check if tuple is associative
60 private
61 enum isAssocTuple(alias T) = expandFieldNames(T.fieldNames)
62     .filter!(x => !x.empty)
63     .array
64     .length;
65 
66 // Forward JSONValue directly
67 auto toJson()(auto ref JSONValue value)
68 {
69     return value;
70 }
71 
72 // Convert to json
73 JSONValue toJson(T)(auto ref T value)
74 {
75     static if( isTuple!T ) {
76     // Tuples
77         static if(isAssocTuple!T) {
78             JSONValue rez;
79             static foreach(enum i, enum subfield; value.fieldNames) {
80                 static if(subfield.empty) {
81                     rez["_" ~ i.to!string] = toJson(value[i]);
82                 } else {
83                     rez[subfield] = toJson(value[i]);
84                 }
85             }
86             return rez;
87         } else {
88             JSONValue[] rez;
89             foreach(ref v; value.expand) {
90                 rez ~= [toJson(v)];
91             }
92             return JSONValue(rez);
93         }
94     } else static if(is(T == enum)) {
95         // Enums
96         static foreach(enum field; allProperties!T ) {
97             if(value == __traits(getMember, T, field)) {
98                 return tuple!("key", "value")(field, cast(OriginalType!T) value)
99                     .toJson();
100             }
101         }
102         assert(0);
103     } else static if( Unqual!T.stringof == "DateTime" ) {
104     // DateTime
105         return JSONValue(value.toISOExtString());
106     } else static if( isArray!T && !isSomeString!T ) {
107     // Arrays
108         return JSONValue(value.map!(x => x.toJson()).array);
109     } else static if( isAssociativeArray!T ) {
110     // AssociativeArrays
111         JSONValue rez;
112         foreach(subfield, ref v; value) {
113             rez[subfield] = toJson(v);
114         }
115         return rez;
116     } else static if( isIterable!T && !isSomeString!T ) {
117     // Iterable
118         return JSONValue(value.map!(x => x.toJson()).array);
119     } else static if( is(T == struct) ) {
120     // Structures
121         JSONValue rez;
122         static foreach(enum field; allProperties!T ) {
123             rez[field] = toJson(__traits(getMember, value, field));
124         }
125         return rez;
126     } else static if( isPointer!T ) {
127     // Pointers
128         return (value is null) ? JSONValue() : toJson(*value);
129     } else static if( isSomeChar!T ) {
130     // Chars
131         return JSONValue([value]);
132     } else {
133     // Other
134         return JSONValue(value);
135     }
136 }
137 
138 // cd source 
139 // rdmd -unittest -main  vest/json/tojson
140 unittest {
141     import std.typecons  : tuple, Tuple;
142     import std.range     : iota;
143     import std.array     : array;
144     import std.algorithm : map, equal;
145     import std.json      : parseJSON;
146     //import std.stdio     : writeln;
147 
148     static struct SubNest {
149         size_t q = 100;
150         auto  r1 = tuple(5, "55");
151         auto  r2 = tuple!("r2")(55);
152         
153         float get_q() const
154         {
155             return q;
156         }
157     }
158 
159     SubNest sn;
160     assert(sn.toJson.toString == `{"q":100,"r1":[5,"55"],"r2":{"r2":55}}`);
161 
162     auto dic = [
163         "one" : SubNest(1),
164         "two" : SubNest(2),
165     ];
166     assert(dic.toJson.toString == `{"one":{"q":1,"r1":[5,"55"],"r2":{"r2":55}},"two":{"q":2,"r1":[5,"55"],"r2":{"r2":55}}}`);
167 
168     auto arr1 = [tuple!("fld1", "fld2")(55, 66), tuple!("fld1", "fld2")(77, 88)];
169     assert(arr1.toJson.toString == `[{"fld1":55,"fld2":66},{"fld1":77,"fld2":88}]`);
170 
171     auto arr2 = [tuple(11, 22), tuple(33, 44)];
172     assert(arr2.toJson.toString == `[[11,22],[33,44]]`);
173 
174     int i1 = 15;
175     int *pi1 = &i1;
176     int *pi2;
177     auto arr3 = [pi1, pi2];
178     assert(arr3.toJson.toString == `[15,null]`);
179 
180     auto tpl1 = tuple(true, 'e', 'Ё', "Hi", iota(0, 3));
181     assert(tpl1.toJson.toString == `[true,"e","Ё","Hi",[0,1,2]]`);
182 
183     static struct MyStruct
184     {
185         int i      = 5;
186         auto rng1 = iota(0, 3);
187         auto rng2 = iota(4, 6).map!( (int v) {return tuple(v, SubNest(5 * v));}  );
188         
189         string str = "Hi";
190 
191         static int q = 55;
192         enum r = 15;
193 
194         string[] strs = ["1", "2", "3"];
195 
196         static struct Nest {
197             int x = 11;
198             SubNest[] sns = [SubNest(1), SubNest(2)];
199         };
200         Nest nest;
201 
202         int * ptr1;
203         int * ptr2;
204 
205         SubNest[string] dic;
206 
207         bool flag = true;
208         char  c1 = 'e';
209         dchar c2 = 'Ё';
210         auto tpl1 = tuple(10, "%");
211         auto tpl2 = tuple!("x", "y", "z")(2, 3, 4);
212         Tuple!(int, "id", string, uint, SubNest) tpl3 = tuple(2, "3", 4, SubNest(101));
213 
214         this(string a){}
215 
216         ~this(){}
217         
218         int get_i() const
219         {
220             return i;
221         }
222         string get_str() const
223         {
224             return str;
225         }
226     }
227 
228     MyStruct mstr;
229     mstr.dic = [
230         "first"  : SubNest(1),
231         "second" : SubNest(2),
232     ];
233     mstr.ptr2 = &mstr.i;
234     assert(mstr.toJson.toString == `{"c1":"e","c2":"Ё","dic":{"first":{"q":1,"r1":[5,"55"],"r2":{"r2":55}},"second":{"q":2,"r1":[5,"55"],"r2":{"r2":55}}},"flag":true,"i":5,"nest":{"sns":[{"q":1,"r1":[5,"55"],"r2":{"r2":55}},{"q":2,"r1":[5,"55"],"r2":{"r2":55}}],"x":11},"ptr1":null,"ptr2":5,"q":55,"r":15,"rng1":[0,1,2],"rng2":[[4,{"q":20,"r1":[5,"55"],"r2":{"r2":55}}],[5,{"q":25,"r1":[5,"55"],"r2":{"r2":55}}]],"str":"Hi","strs":["1","2","3"],"tpl1":[10,"%"],"tpl2":{"x":2,"y":3,"z":4},"tpl3":{"_1":"3","_2":4,"_3":{"q":101,"r1":[5,"55"],"r2":{"r2":55}},"id":2}}`);
235 
236     // Structure has template methods
237     static struct HasTpl
238     {
239         int i      = 5;
240         this(string a){}
241         ~this(){}
242         int get_i() const {return i;}
243         int get_itpl()() const {return i;}
244         int get_itpl1(T)(T t) const {return i*t;}
245     }
246     HasTpl htpl;
247     assert(htpl.toJson.toString == `{"i":5}`);
248 
249     // Forward json directly
250     assert(`{"q":100,"r":[1,2]}`.parseJSON.toJson.toString == `{"q":100,"r":[1,2]}`);
251 
252     // Enums
253     enum kla_t : int {kla1 = 1, kla2, kla3}
254     assert(kla_t.kla2.toJson.toString == `{"key":"kla2","value":2}`);
255 
256     enum sta_t : string
257     {
258         sta1 = "sta1_v",
259         sta2 = "sta2_v",
260         sta3 = "sta3_v"
261     }
262     assert(sta_t.sta3.toJson.toString == `{"key":"sta3","value":"sta3_v"}`);
263 }