1 // Written in D the D Programming Language
2 /**
3 任意の構造体やクラスをJSONに変換したり、その逆変換を行う
4 */
5 module graphite.utils.json;
6 
7 import std.algorithm;
8 import std.array;
9 import std.complex;
10 import std.conv;
11 import std.exception;
12 import std.json;
13 import std.process;
14 import std.range;
15 import std.string;
16 import std.traits;
17 import std.typecons;
18 import std.typetuple;
19 import std.variant;
20 
21 
22 private template _StaticIota(size_t M, size_t N)
23 if(M <= N)
24 {
25     static if(M == N)
26         alias _StaticIota = TypeTuple!();
27     else
28         alias _StaticIota = TypeTuple!(M, _StaticIota!(M+1, N));
29 }
30 
31 
32 class JSONException : Exception
33 {
34     this(string msg, string file = null, size_t line = 0)
35     {
36         super(msg, file, line);
37     }
38 }
39 
40 
41 template JSONEnv(alias overloads)
42 {
43     /**
44 
45     */
46     void fromJSONValue(T)(JSONValue json, ref T dst)
47     {
48         static if(is(typeof(overloads.fromJSONValueImpl(json, dst))))
49             overloads.fromJSONValueImpl(json, dst);
50         else static if(is(typeof(T.fromJSONValueImpl(json)) : T))
51             dst = T.fromJSONValueImpl(json);
52         else
53             fromJSONValueImpl(json, dst);
54     }
55 
56 
57     ///
58     JSONValue toJSONValue(T)(auto ref T t)
59     {
60         static if(is(typeof(overloads.toJSONValueImpl(t)) == JSONValue))
61             return overloads.toJSONValueImpl(forward!t);
62         else static if(is(typeof(t.toJSONValueImpl()) == JSONValue))
63             return t.toJSONValueImpl();
64         else
65             return toJSONValueImpl(forward!t);
66     }
67 
68 
69     ///
70     JSONValue toJSONValueImpl(T)(T value)
71     if(is(T == JSONValue))
72     {
73         return value;
74     }
75 
76 
77     ///
78     JSONValue toJSONValueImpl(T)(T value)
79     if(is(T == typeof(null)))
80     out(result){
81         assert(result.type == JSON_TYPE.NULL);
82     }
83     body{
84         return JSONValue(null);
85     }
86 
87 
88     ///
89     JSONValue toJSONValueImpl(T)(T value)
90     if(isSomeString!T)
91     out(result){
92         assert(result.type == JSON_TYPE.STRING);
93     }
94     body{
95         return JSONValue(value.to!string);
96     }
97 
98 
99     ///
100     JSONValue toJSONValueImpl(T)(T value)
101     if(isUnsigned!T && isIntegral!T)
102     out(result){
103         assert(result.type == JSON_TYPE.UINTEGER);
104     }
105     body{
106         return JSONValue(value);
107     }
108 
109 
110     ///
111     JSONValue toJSONValueImpl(T)(T value)
112     if(isSigned!T && isIntegral!T)
113     out(result){
114         assert(result.type == JSON_TYPE.INTEGER);
115     }
116     body{
117         return JSONValue(value);
118     }
119 
120 
121     ///
122     JSONValue toJSONValueImpl(T)(T value)
123     if(is(T == bool))
124     out(result){
125         assert(result.type == JSON_TYPE.TRUE || result.type == JSON_TYPE.FALSE);
126     }
127     body{
128         return JSONValue(value);
129     }
130 
131 
132     ///
133     JSONValue toJSONValueImpl(T)(T value)
134     if(isFloatingPoint!T)
135     out(result){
136         assert(result.type == JSON_TYPE.FLOAT);
137     }
138     body{
139         return JSONValue(value);
140     }
141 
142 
143     ///
144     JSONValue toJSONValueImpl(R)(R range)
145     if(isInputRange!R && !isSomeString!R)
146     out(result){
147         assert(result.type == JSON_TYPE.ARRAY);
148     }
149     body{
150         auto app = appender!(JSONValue[])();
151 
152         app.put(range.map!(a => toJSONValue(a)));
153 
154         return JSONValue(app.data);
155     }
156 
157 
158     ///
159     JSONValue toJSONValueImpl(AA)(AA aa)
160     if(isAssociativeArray!AA)
161     out(result){
162         assert(result.type == JSON_TYPE.OBJECT);
163     }
164     body{
165         JSONValue[string] dst;
166         foreach(k, v; aa){
167             static if(is(typeof(k) : string))
168                 dst[k] = toJSONValue(v);
169             else
170                 dst[k.to!string()] = toJSONValue(v);
171         }
172 
173         return JSONValue(dst);
174     }
175 
176 
177     /// 
178     JSONValue toJSONValueImpl(T)(T v)
179     if(is(T == Variant))
180     {
181         if(!v.hasValue)
182             return JSONValue(null);
183         else
184         {
185             bool bSuccess;
186             JSONValue jv;
187 
188             alias TT = TypeTuple!(Variant, Variant[], Variant[string], Variant[Variant],
189                                   byte, ubyte, short, ushort,
190                                   int, uint, long, ulong,
191                                   float, double, real,
192                                   string, wstring, dstring,
193                                   bool, typeof(null));
194 
195             foreach(U; TT){
196                 if(auto p = v.peek!U){
197                     jv = toJSONValue(*p);
198                     bSuccess =  true;
199                     break;
200                 }
201             }
202 
203             if(!bSuccess)
204                 jv = JSONValue(v.to!string());
205 
206             return jv;
207         }
208     }
209 
210 
211     private string createFromJSONValueExceptionMsg(T)(JSONValue json)
212     {
213         return "cannot convert to '" ~ T.stringof ~ "' from "`"` ~ toJSON(&json) ~ `"`;
214     }
215 
216 
217     ///
218     void fromJSONValueImpl(T)(JSONValue json, ref T dst)
219     if(is(T == JSONValue))
220     {
221         dst = json;
222     }
223 
224 
225     ///
226     void fromJSONValueImpl(T)(JSONValue json, ref T dst)
227     if(is(T == typeof(null)))
228     {
229         enforceEx!JSONException(json.type == JSON_TYPE.NULL, createFromJSONValueExceptionMsg!T(json));
230     }
231 
232 
233     ///
234     void fromJSONValueImpl(T)(JSONValue json, ref T dst)
235     if(isSomeString!T)
236     {
237         enforceEx!JSONException(json.type == JSON_TYPE.STRING, createFromJSONValueExceptionMsg!T(json));
238         dst = json.str.to!T;
239     }
240 
241 
242     ///
243     void fromJSONValueImpl(T)(JSONValue json, ref T dst)
244     if(isIntegral!T && isUnsigned!T)
245     {
246         enforceEx!JSONException(json.type == JSON_TYPE.UINTEGER || json.type == JSON_TYPE.INTEGER, createFromJSONValueExceptionMsg!T(json));
247 
248         if(json.type == JSON_TYPE.UINTEGER)
249             dst = json.uinteger.to!T();
250         else
251             dst = json.integer.to!T();
252     }
253 
254 
255     ///
256     void fromJSONValueImpl(T)(JSONValue json, ref T dst)
257     if(isIntegral!T && isSigned!T)
258     {
259         enforceEx!JSONException(json.type == JSON_TYPE.INTEGER || json.type == JSON_TYPE.UINTEGER, createFromJSONValueExceptionMsg!T(json));
260 
261         if(json.type == JSON_TYPE.INTEGER)
262             dst = json.integer.to!T();
263         else
264             dst = json.uinteger.to!T();
265     }
266 
267 
268     ///
269     void fromJSONValueImpl(T)(JSONValue json, ref T dst)
270     if(is(T == bool))
271     {
272         enforceEx!JSONException(json.type == JSON_TYPE.TRUE || json.type == JSON_TYPE.FALSE, createFromJSONValueExceptionMsg!T(json));
273         dst = json.type == JSON_TYPE.TRUE;
274     }
275 
276 
277     ///
278     void fromJSONValueImpl(T)(JSONValue json, ref T dst)
279     if(isFloatingPoint!T)
280     {
281         enforceEx!JSONException(json.type == JSON_TYPE.FLOAT
282                              || json.type == JSON_TYPE.INTEGER
283                              || json.type == JSON_TYPE.UINTEGER, createFromJSONValueExceptionMsg!T(json));
284         
285         if(json.type == JSON_TYPE.FLOAT)
286             dst = json.floating;
287         else if(json.type == JSON_TYPE.INTEGER)
288             dst = json.integer;
289         else
290             dst = json.uinteger;
291     }
292 
293 
294     ///
295     void fromJSONValueImpl(T)(JSONValue json, ref T dst)
296     if(isArray!T && !isSomeString!T)
297     {
298         enforceEx!JSONException(json.type == JSON_TYPE.ARRAY, createFromJSONValueExceptionMsg!T(json));
299 
300         T data = new T(json.array.length);
301 
302         foreach(i, e; json.array){
303             typeof(data[i]) elem;
304             fromJSONValue(e, elem);
305             data[i] = elem;
306         }
307 
308         dst = data;
309     }
310 
311 
312     ///
313     void fromJSONValueImpl(T)(JSONValue json, ref T dst)
314     if(isInputRange!T && isOutputRange!(T, Unqual!(ElementType!T)) && !isArray!T)
315     {
316         enforceEx!JSONException(json.type == JSON_TYPE.ARRAY, createFromJSONValueExceptionMsg!T(json));
317 
318         foreach(e; json.array){
319             alias Elem = Unqual!(ElementType!T);
320             Elem elem;
321             fromJSONValue(e, elem);
322             dst.put(elem);
323         }
324     }
325 
326 
327     ///
328     void fromJSONValueImpl(T)(JSONValue json, ref T dst)
329     if(isAssociativeArray!(T))
330     {
331         enforceEx!JSONException(json.type == JSON_TYPE.OBJECT, createFromJSONValueExceptionMsg!T(json));
332 
333         alias V = typeof(T.init.values[0]);
334         alias K = typeof(T.init.keys[0]);
335 
336         foreach(k, v; json.object){
337             V elem;
338             fromJSONValue(v, elem);
339             dst[k.to!K] = elem;
340         }
341     }
342 
343 
344     ///
345     void fromJSONValueImpl(T)(JSONValue json, ref T dst)
346     if(is(T == Variant))
347     {
348         static void impl(T)(JSONValue json, ref Variant dst)
349         {
350             T v;
351             fromJSONValue(json, v);
352             dst = v;
353         }
354 
355 
356         final switch(json.type)
357         {
358           case JSON_TYPE.STRING:
359             impl!string(json, dst);
360             return;
361 
362           case JSON_TYPE.INTEGER:
363             impl!long(json, dst);
364             return;
365 
366           case JSON_TYPE.UINTEGER:
367             impl!ulong(json, dst);
368             return;
369 
370           case JSON_TYPE.FLOAT:
371             impl!real(json, dst);
372             return;
373 
374           case JSON_TYPE.OBJECT:
375             impl!(Variant[string])(json, dst);
376             return;
377 
378           case JSON_TYPE.ARRAY:
379             impl!(Variant[])(json, dst);
380             return;
381 
382           case JSON_TYPE.TRUE, JSON_TYPE.FALSE:
383             impl!(bool)(json, dst);
384             return;
385 
386           case JSON_TYPE.NULL:
387             impl!(typeof(null))(json, dst);
388             return;
389         }
390     }
391 }
392 
393 
394 /**
395 任意のユーザー定義型をJSONでのObjectに変換する際に便利なテンプレート
396 */
397 mixin template JSONObject(fields...)
398 if(fields.length && fields.length % 2 == 0)
399 {
400     JSONValue toJSONValueImpl() @property
401     {
402         JSONValue[string] aa;
403 
404         foreach(i; _StaticIota!(0, fields.length))
405         {
406             static if(i % 2 == 0)
407             {
408                 static assert(is(typeof(fields[i]) == string));
409 
410                 static if(is(typeof(mixin(fields[i+1]))))
411                     aa[fields[i]] = toJSONValue(mixin(fields[i+1]));
412                 else
413                     aa[fields[i]] = toJSONValue(fields[i+1]);
414             }
415         }
416 
417         return JSONValue(aa);
418     }
419 
420 
421     private ref typeof(this) fromJSONValueImpl_(JSONValue jv)
422     {
423         foreach(i; _StaticIota!(0, fields.length))
424         {
425             static if(i % 2 == 0)
426             {
427                 static if(is(typeof(&(fields[i+1]())) == U*, U))
428                     fromJSONValue(jv.object[fields[i]], fields[i+1]());
429                 else static if(is(typeof(&(fields[i+1])) == U*, U))
430                     fromJSONValue(jv.object[fields[i]], fields[i+1]);
431                 else static if(is(typeof(&(mixin(fields[i+1]))) == U*, U) &&
432                                !is(typeof(&(mixin(fields[i+1]))) == V function(W), V, W...))
433                 {
434                     fromJSONValue(jv.object[fields[i]], mixin(fields[i+1]));
435                 }
436                 else
437                 {
438                     static if(is(typeof(fields[i+1]())))    // property
439                         alias X = typeof({auto x = fields[i+1](); return x;}());
440                     else static if(is(typeof(mixin(fields[i+1])())))    // property(string)
441                         alias X = typeof(mixin(fields[i+1])());
442                     else
443                         alias X = ParameterTypeTuple!(fields[i+1])[0];
444 
445                     X x;
446                     fromJSONValue(jv.object[fields[i]], x);
447 
448                     static if(is(typeof(mixin(fields[i+1]))))
449                         mixin(fields[i+1]) = x;
450                     else
451                         fields[i+1](x);
452                 }
453             }
454         }
455 
456 
457         return this;
458     }
459 
460 
461     static typeof(this) fromJSONValueImpl(JSONValue jv)
462     {
463         typeof(this) dst;
464         dst.fromJSONValueImpl_(jv);
465         return dst;
466     }
467 }
468 
469 
470 ///
471 unittest{
472     // Custom JSON Convertor
473     // ユーザーは、任意の型をJSONへ変換するための変換器を定義できる
474     static struct CustomJSONConvertor
475     {
476         static { mixin JSONEnv!null _dummy; }
477 
478         static JSONValue toJSONValueImpl(string str)
479         {
480             return _dummy.toJSONValueImpl("Custom Convertor-String : " ~ str);
481         }
482 
483 
484         static void fromJSONValueImpl(JSONValue json, ref string str)
485         {
486             assert(json.type == JSON_TYPE.STRING, "Error");
487             str = json.str.find(" : ").drop(3);
488         }
489     }
490 
491 
492     // グローバルなsetterとgetterだと仮定
493     static struct Foo
494     {
495         int gloF() @property { return _gloF; }
496         void gloF(int a) @property { _gloF = a; }
497 
498         static int _gloF = 12345;
499     }
500 
501 
502     // グローバル変数だと仮定
503     static string gloG = "global variable";
504 
505 
506     // JSONへ変換したり、JSONから変換する対象のオブジェクト
507     static struct S1
508     {
509         // JSON Convertorの定義
510         // 通常はモジュールで定義すればよい
511         static {
512             mixin JSONEnv!CustomJSONConvertor;
513         }
514 
515 
516         // refで返すプロパティ
517         ref real flt() @property
518         {
519             return _flt;
520         }
521 
522 
523         // getter
524         int[] arr() @property
525         {
526             return _arr;
527         }
528 
529         // setter
530         void arr(int[] arr) @property
531         {
532             _arr = arr;
533         }
534 
535 
536         // JSONでのオブジェクトの定義
537         mixin JSONObject!("intA", a,                // メンバ変数
538                           "strB", "b",              // メンバ変数(文字列)
539                           "fltC", flt,              // refを返すメンバ関数(プロパティ)
540                           "arrD", "arr",            // setter, getter
541                           "aasE", "aas",            // static setter, getter
542                           "gloF", "Foo.init.gloF",  // global setter, getter
543                           "gloG", gloG,             // グローバル変数など外部スコープの変数
544                           );
545 
546 
547       private:
548         int a;
549         string b;
550         real _flt;
551         int[] _arr;
552 
553 
554       static:
555 
556         // staticなgetter
557         int[int] aas() @property
558         {
559             return [1 : 2, 2 : 3, 3 : 4, 4 : 5];
560         }
561 
562 
563         // staticなsetter
564         void aas(int[int] aa) @property
565         {}
566     }
567 
568     auto s1 = S1(12, "foo", 2.0, [1, 2, 3, 4]);
569     auto jv = S1.toJSONValue(s1);
570 
571     auto jvtext = parseJSON(`{"gloF":12345,"strB":"Custom Convertor-String : foo","fltC":2,"gloG":"Custom Convertor-String : global variable","intA":12,"aasE":{"4":5,"1":2,"2":3,"3":4},"arrD":[1,2,3,4]}`);
572     assert(toJSON(&jv) == toJSON(&jvtext));
573 
574     auto s2 = S1.fromJSONValueImpl(jv);
575     auto s3 = S1.fromJSONValueImpl(jvtext);
576 
577     assert(s1 == s2);
578     assert(s2 == s3);
579 
580     assert(Foo.init.gloF == 12345);
581     assert(gloG == "global variable");
582 }
583 
584 
585 /**
586 JSON Objectを構築します
587 */
588 struct JSONObjectType(alias jsonEnv, fields...)
589 if(fields.length && isValidJSONObjectTypeFields!fields)
590 {
591     static {
592         mixin JSONEnv!jsonEnv;
593     }
594 
595     mixin(genStructField);
596     mixin(`mixin JSONObject!(` ~ genMixinFields ~ `);`);
597 
598   private:
599   static:
600     string genStructField()
601     {
602         string dst;
603 
604         foreach(i; _StaticIota!(0, fields.length))
605         {
606             static if(i % 2 == 0)
607             {
608                 dst ~= fields[i].stringof;  // type
609                 dst ~= " ";
610                 dst ~= fields[i+1];         // identifier
611                 dst ~= ";\n";
612             }
613         }
614 
615         return dst;
616     }
617 
618 
619     string genMixinFields()
620     {
621         string dst;
622 
623         foreach(i; _StaticIota!(0, fields.length))
624         {
625             static if(i % 2 == 0)
626             {
627                 dst ~= `"` ~ fields[i+1] ~ `",`;     // tag
628                 dst ~= `"` ~ fields[i+1] ~ `",`;     // identifier
629             }
630         }
631 
632         return dst;
633     }
634 }
635 
636 
637 unittest{
638     JSONObjectType!(null,
639                     int,        "intA",
640                     string,     "strB",
641                     float,      "fltC") jot;
642 
643     jot.intA = 12;
644     jot.strB = "foo-bar";
645     jot.fltC = 12.5;
646 
647     auto x = jot.toJSONValueImpl();
648     auto y = parseJSON(`{"strB":"foo-bar","fltC":12.5,"intA":12}`);
649     assert(toJSON(&x) == toJSON(&y));
650 
651     assert(typeof(jot).fromJSONValueImpl(x) == jot);
652     assert(typeof(jot).fromJSONValueImpl(y) == jot);
653 }
654 
655 
656 private
657 template isValidJSONObjectTypeFields(fields...)
658 {
659     //template isValidImpl(T, string name) { enum isValidImpl = true; }
660     enum isValidImpl_(T, string name) = name.length;
661     enum isValidImpl(X...) = is(typeof({static assert(isValidImpl_!X);}));
662 
663     static if(fields.length)
664     {
665         static if(fields.length % 2 == 0)
666             enum isValidJSONObjectTypeFields = isValidImpl!(fields[0 .. 2])
667                                             && isValidJSONObjectTypeFields!(fields[2 .. $]);
668         else
669             enum isValidJSONObjectTypeFields = false;
670     }
671     else
672         enum isValidJSONObjectTypeFields = true;
673 }