1 /**
2  * JSON module
3  *
4  * Copyright: (c) 2015-2020, Milofon Project.
5  * License: Subject to the terms of the BSD 3-Clause License, as written in the included LICENSE.md file.
6  * Author: <m.galanin@milofon.pro> Maksim Galanin
7  * Date: 2020-03-09
8  */
9 
10 module uniconf.json;
11 
12 private
13 {
14     import std.json : JSONValue, JSONType, parseJSON, JSONException, toJSON;
15 
16     import uniconf.core : UniConfException;
17     import uninode.node : isUniNode;
18 }
19 
20 
21 /**
22  * Conert Json to UniConf
23  */
24 UniConf toUniConf(UniConf)(auto ref const JSONValue root) pure @safe
25     if (isUniNode!UniConf)
26 {
27     UniConf convert(ref const JSONValue node) pure @trusted
28     {
29         switch(node.type)
30         {
31             case JSONType.NULL:
32                 return UniConf();
33             case JSONType.TRUE:
34                 return UniConf(true);
35             case JSONType.FALSE:
36                 return UniConf(false);
37             case JSONType.INTEGER:
38                 return UniConf(node.integer);
39             case JSONType.UINTEGER:
40                 return UniConf(node.uinteger);
41             case JSONType.FLOAT:
42                 return UniConf(node.floating);
43             case JSONType.STRING:
44                 return UniConf(node.str);
45             case JSONType.ARRAY:
46             {
47                 size_t len = node.array.length;
48                 UniConf[] arr = new UniConf[len];
49                 foreach(size_t i, JSONValue ch; node.array)
50                     arr[i] = convert(ch);
51                 return UniConf(arr);
52             }
53             case JSONType.OBJECT:
54             {
55                 UniConf[string] map;
56                 foreach (string key, JSONValue ch; node.object)
57                     map[key] = convert(ch);
58                 return UniConf(map);
59             }
60             default:
61                 return UniConf();
62         }
63     }
64 
65     return convert(root);
66 }
67 
68 
69 /**
70  * Convert UniConf to Json
71  */
72 JSONValue toJson(UniConf)(auto ref const UniConf root) @safe
73     if (isUniNode!UniConf)
74 {
75     JSONValue convert(ref const UniConf node) @trusted
76     {
77         switch(node.tag)
78         {
79             case UniConf.Tag.nil:
80                 return JSONValue();
81             case UniConf.Tag.boolean:
82                 return JSONValue(node.get!bool);
83             case UniConf.Tag.integer:
84                 return JSONValue(node.get!long);
85             case UniConf.Tag.uinteger:
86                 return JSONValue(node.get!ulong);
87             case UniConf.Tag.floating:
88                 return JSONValue(node.get!double);
89             case UniConf.Tag.text:
90                 return JSONValue(node.get!string);
91             case UniConf.Tag.sequence:
92             {
93                 size_t len = node.length;
94                 JSONValue[] arr = new JSONValue[len];
95                 foreach(size_t i, ref const UniConf ch; node)
96                     arr[i] = convert(ch);
97                 return JSONValue(arr);
98             }
99             case UniConf.Tag.mapping:
100             {
101                 JSONValue[string] map;
102                 foreach (string key, ref const UniConf ch; node)
103                     map[key] = convert(ch);
104                 return JSONValue(map);
105             }
106             default:
107                 return JSONValue();
108         }
109     }
110 
111     return convert(root);
112 }
113 
114 @("Should work convert json to config and back")
115 @safe unittest
116 {
117     import uninode.node : UniNode;
118 
119     const val = JSONValue([JSONValue(1), JSONValue("hello"), JSONValue(["one": JSONValue(1)])]);
120     const conf = val.toUniConf!UniNode();
121     const js = conf.toJson();
122     assert (js == val);
123 }
124 
125 
126 /**
127  * Parse UniConf from string
128  */
129 UniConf parseJson(UniConf)(string data) @safe
130     if (isUniNode!UniConf)
131 {
132     JSONValue root;
133     try
134         root = parseJSON(data);
135     catch (JSONException e)
136         throw new UniConfException("Error loading json from a string:",
137                 e.file, e.line, e);
138     return toUniConf!UniConf(root);
139 }
140 
141 
142 /**
143  * Convert UniConf to json string
144  */
145 string saveJson(UniConf)(auto ref const UniConf node, bool pretty=false) @safe
146 {
147     const json = toJson!UniConf(node);
148     return toJSON(json, pretty);
149 }
150 
151 @("Should work parseJson and saveJson method")
152 @safe unittest
153 {
154     import uninode.tree : UniTree;
155 
156     enum json = `{"client":{"host":"localhost","port":44}}`;
157 
158     const conf = json.parseJson!UniTree;
159     assert (conf.get!int("client.port") == 44);
160     assert (conf.get!string("client.host") == "localhost");
161 
162     const js = saveJson(conf);
163     assert (js == json);
164 }
165