1 /**
2   * Contains functions for serializing JSON data.
3   *
4   * Authors: <a href="https://github.com/rcorre">rcorre</a>
5 	* License: <a href="http://opensource.org/licenses/MIT">MIT</a>
6 	* Copyright: Copyright © 2015, rcorre
7   * Date: 3/23/15
8   */
9 module jsonizer.tojson;
10 
11 import std.json;
12 import std.traits;
13 import std.conv : to;
14 import std.file : write;
15 import std.exception : enforce;
16 import std.typecons : staticIota, Flag;
17 
18 // Primitive Type Conversions -----------------------------------------------------------
19 /// convert a bool to a JSONValue
20 JSONValue toJSON(T : bool)(T val) {
21   return JSONValue(val);
22 }
23 
24 /// Serialize a boolean.
25 unittest {
26   assert(false.toJSON == JSONValue(false));
27   assert(true.toJSON == JSONValue(true));
28 }
29 
30 /// convert a string to a JSONValue
31 JSONValue toJSON(T : string)(T val) {
32   return JSONValue(val);
33 }
34 
35 /// Serialize a string.
36 unittest {
37   assert("bork".toJSON == JSONValue("bork"));
38 }
39 
40 /// convert a floating point value to a JSONValue
41 JSONValue toJSON(T : real)(T val) if (!is(T == enum)) {
42   return JSONValue(val);
43 }
44 
45 /// Serialize a floating-point value.
46 unittest {
47   assert(4.1f.toJSON == JSONValue(4.1f));
48 }
49 
50 /// convert a signed integer to a JSONValue
51 JSONValue toJSON(T : long)(T val) if (isSigned!T && !is(T == enum)) {
52   return JSONValue(val);
53 }
54 
55 /// Serialize a signed integer.
56 unittest {
57   auto j3 = toJSON(41);
58   assert(4.toJSON == JSONValue(4));
59   assert(4L.toJSON == JSONValue(4L));
60 }
61 
62 /// convert an unsigned integer to a JSONValue
63 JSONValue toJSON(T : ulong)(T val) if (isUnsigned!T && !is(T == enum)) {
64   return JSONValue(val);
65 }
66 
67 /// Serialize an unsigned integer.
68 unittest {
69   assert(41u.toJSON == JSONValue(41u));
70 }
71 
72 /// convert an enum name to a JSONValue
73 JSONValue toJSON(T)(T val) if (is(T == enum)) {
74   JSONValue json;
75   json.str = to!string(val);
76   return json;
77 }
78 
79 /// Enums are serialized by name.
80 unittest {
81   enum Category { one, two }
82 
83   assert(Category.one.toJSON.str == "one");
84   assert(Category.two.toJSON.str == "two");
85 }
86 
87 /// convert a homogenous array into a JSONValue array
88 JSONValue toJSON(T)(T args) if (isArray!T && !isSomeString!T) {
89   static if (isDynamicArray!T) {
90     if (args is null) { return JSONValue(null); }
91   }
92   JSONValue[] jsonVals;
93   foreach(arg ; args) {
94     jsonVals ~= toJSON(arg);
95   }
96   JSONValue json;
97   json.array = jsonVals;
98   return json;
99 }
100 
101 /// Serialize a homogenous array.
102 unittest {
103   auto json = [1, 2, 3].toJSON;
104   assert(json.type == JSON_TYPE.ARRAY);
105   assert(json.array[0].integer == 1);
106   assert(json.array[1].integer == 2);
107   assert(json.array[2].integer == 3);
108 }
109 
110 /// convert a set of heterogenous values into a JSONValue array
111 JSONValue toJSON(T...)(T args) {
112   JSONValue[] jsonVals;
113   foreach(arg ; args) {
114     jsonVals ~= toJSON(arg);
115   }
116   JSONValue json;
117   json.array = jsonVals;
118   return json;
119 }
120 
121 /// Serialize a heterogenous array.
122 unittest {
123   auto json = toJSON(1, "hi", 0.4);
124   assert(json.type == JSON_TYPE.ARRAY);
125   assert(json.array[0].integer  == 1);
126   assert(json.array[1].str      == "hi");
127   assert(json.array[2].floating == 0.4);
128 }
129 
130 /// convert a associative array into a JSONValue object
131 JSONValue toJSON(T)(T map) if (isAssociativeArray!T) {
132   assert(is(KeyType!T : string), "toJSON requires string keys for associative array");
133   if (map is null) { return JSONValue(null); }
134   JSONValue[string] obj;
135   foreach(key, val ; map) {
136     obj[key] = toJSON(val);
137   }
138   JSONValue json;
139   json.object = obj;
140   return json;
141 }
142 
143 /// Serialize an associative array.
144 unittest {
145   auto json = ["a" : 1, "b" : 2, "c" : 3].toJSON;
146   assert(json.type == JSON_TYPE.OBJECT);
147   assert(json.object["a"].integer == 1);
148   assert(json.object["b"].integer == 2);
149   assert(json.object["c"].integer == 3);
150 }
151 
152 /// Convert a user-defined type to json.
153 /// See `jsonizer.jsonize` for info on how to mark your own types for serialization.
154 JSONValue toJSON(T)(T obj) if (!isBuiltinType!T) {
155   static if (is (T == class)) {
156     if (obj is null) { return JSONValue(null); }
157   }
158   return obj.convertToJSON();
159 }
160 
161 /// Serialize an instance of a user-defined type to a json object.
162 unittest {
163   import jsonizer.jsonize;
164   import jsonizer.fromjson;
165 
166   static struct Foo {
167     mixin JsonizeMe;
168 
169     @jsonize {
170       int i;
171       string[] a;
172     }
173   }
174 
175   auto foo = Foo(12, [ "a", "b" ]);
176   auto json = foo.toJSON();
177 
178   assert(json.fromJSON!int("i") == 12);
179   assert(json.fromJSON!(string[])("a") == [ "a", "b" ]);
180 }
181 
182 /// Whether to nicely format json string.
183 alias PrettyJson = Flag!"PrettyJson";
184 
185 /// Convert an instance of some type `T` directly into a json-formatted string.
186 /// Params:
187 ///   T      = type of object to convert
188 ///   obj    = object to convert to sjon
189 ///   pretty = whether to prettify string output
190 string toJSONString(T)(T obj, PrettyJson pretty = PrettyJson.yes) {
191   auto json = obj.toJSON!T;
192   return pretty ? json.toPrettyString : json.toString;
193 }
194 
195 unittest {
196   assert([1, 2, 3].toJSONString(PrettyJson.no) == "[1,2,3]");
197   assert([1, 2, 3].toJSONString(PrettyJson.yes) == "[\n    1,\n    2,\n    3\n]");
198 }
199 
200 /// Write a jsonizeable object to a file.
201 /// Params:
202 ///   path = filesystem path to write json to
203 ///   obj  = object to convert to json and write to path
204 void writeJSON(T)(string path, T obj) {
205   auto json = toJSON!T(obj);
206   path.write(json.toPrettyString);
207 }
208 
209 unittest {
210   import std.json : parseJSON;
211   import std.path : buildPath;
212   import std.uuid : randomUUID;
213   import std.file : tempDir, readText, mkdirRecurse;
214 
215   auto dir = buildPath(tempDir(), "jsonizer_writejson_test");
216   mkdirRecurse(dir);
217   auto file = buildPath(dir, randomUUID().toString);
218 
219   file.writeJSON([1, 2, 3]);
220 
221   auto json = file.readText.parseJSON;
222   assert(json.array[0].integer == 1);
223   assert(json.array[1].integer == 2);
224   assert(json.array[2].integer == 3);
225 }