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;
14 import std.file;
15 import std.exception;
16 import std.typecons;
17 import jsonizer.common;
18 
19 /// convert a JSONValue to a JSONValue (identity)
20 JSONValue toJSON(JSONValue val) {
21   return val;
22 }
23 
24 /// convert a bool to a JSONValue
25 JSONValue toJSON(T : bool)(T val) {
26   return JSONValue(val);
27 }
28 
29 /// Serialize a boolean.
30 unittest {
31   assert(false.toJSON == JSONValue(false));
32   assert(true.toJSON == JSONValue(true));
33 }
34 
35 /// convert a string to a JSONValue
36 JSONValue toJSON(T : string)(T val) {
37   return JSONValue(val);
38 }
39 
40 /// Serialize a string.
41 unittest {
42   assert("bork".toJSON == JSONValue("bork"));
43 }
44 
45 /// convert a floating point value to a JSONValue
46 JSONValue toJSON(T : real)(T val) if (!is(T == enum)) {
47   return JSONValue(val);
48 }
49 
50 /// Serialize a floating-point value.
51 unittest {
52   assert(4.1f.toJSON == JSONValue(4.1f));
53 }
54 
55 /// convert a signed integer to a JSONValue
56 JSONValue toJSON(T : long)(T val) if (isSigned!T && !is(T == enum)) {
57   return JSONValue(val);
58 }
59 
60 /// Serialize a signed integer.
61 unittest {
62   auto j3 = toJSON(41);
63   assert(4.toJSON == JSONValue(4));
64   assert(4L.toJSON == JSONValue(4L));
65 }
66 
67 /// convert an unsigned integer to a JSONValue
68 JSONValue toJSON(T : ulong)(T val) if (isUnsigned!T && !is(T == enum)) {
69   return JSONValue(val);
70 }
71 
72 /// Serialize an unsigned integer.
73 unittest {
74   assert(41u.toJSON == JSONValue(41u));
75 }
76 
77 /// convert an enum name to a JSONValue
78 JSONValue toJSON(T)(T val) if (is(T == enum)) {
79   JSONValue json;
80   json.str = to!string(val);
81   return json;
82 }
83 
84 /// Enums are serialized by name.
85 unittest {
86   enum Category { one, two }
87 
88   assert(Category.one.toJSON.str == "one");
89   assert(Category.two.toJSON.str == "two");
90 }
91 
92 /// convert a homogenous array into a JSONValue array
93 JSONValue toJSON(T)(T args) if (isArray!T && !isSomeString!T) {
94   static if (isDynamicArray!T) {
95     if (args is null) { return JSONValue(null); }
96   }
97   JSONValue[] jsonVals;
98   foreach(arg ; args) {
99     jsonVals ~= toJSON(arg);
100   }
101   JSONValue json;
102   json.array = jsonVals;
103   return json;
104 }
105 
106 /// Serialize a homogenous array.
107 unittest {
108   auto json = [1, 2, 3].toJSON;
109   assert(json.type == JSONType.array);
110   assert(json.array[0].integer == 1);
111   assert(json.array[1].integer == 2);
112   assert(json.array[2].integer == 3);
113 }
114 
115 /// convert a set of heterogenous values into a JSONValue array
116 JSONValue toJSON(T...)(T args) {
117   JSONValue[] jsonVals;
118   foreach(arg ; args) {
119     jsonVals ~= toJSON(arg);
120   }
121   JSONValue json;
122   json.array = jsonVals;
123   return json;
124 }
125 
126 /// Serialize a heterogenous array.
127 unittest {
128   auto json = toJSON(1, "hi", 0.4);
129   assert(json.type == JSONType.array);
130   assert(json.array[0].integer  == 1);
131   assert(json.array[1].str      == "hi");
132   assert(json.array[2].floating == 0.4);
133 }
134 
135 /// convert a associative array into a JSONValue object
136 JSONValue toJSON(T)(T map) if (isAssociativeArray!T) {
137   assert(is(KeyType!T : string), "toJSON requires string keys for associative array");
138   if (map is null) { return JSONValue(null); }
139   JSONValue[string] obj;
140   foreach(key, val ; map) {
141     obj[key] = toJSON(val);
142   }
143   JSONValue json;
144   json.object = obj;
145   return json;
146 }
147 
148 /// Serialize an associative array.
149 unittest {
150   auto json = ["a" : 1, "b" : 2, "c" : 3].toJSON;
151   assert(json.type == JSONType.object);
152   assert(json.object["a"].integer == 1);
153   assert(json.object["b"].integer == 2);
154   assert(json.object["c"].integer == 3);
155 }
156 
157 /// Convert a user-defined type to json.
158 /// See `jsonizer.jsonize` for info on how to mark your own types for serialization.
159 JSONValue toJSON(T)(T obj) if (!isBuiltinType!T) {
160   static if (is (T == class))
161     if (obj is null) { return JSONValue(null); }
162 
163   JSONValue[string] keyValPairs;
164 
165   foreach(member ; T._membersWithUDA!jsonize) {
166     enum key = jsonKey!(T, member);
167 
168     auto output = JsonizeOut.unspecified;
169     foreach (attr ; T._getUDAs!(member, jsonize))
170       if (attr.perform_out != JsonizeOut.unspecified)
171         output = attr.perform_out;
172 
173     if (output == JsonizeOut.no) continue;
174 
175     auto val = obj._readMember!member;
176     if (output == JsonizeOut.opt && isInitial(val)) continue;
177 
178     keyValPairs[key] = toJSON(val);
179   }
180 
181   std.json.JSONValue json;
182   json.object = keyValPairs;
183   return json;
184 }
185 
186 /// Serialize an instance of a user-defined type to a json object.
187 unittest {
188   import jsonizer.jsonize;
189 
190   static struct Foo {
191     mixin JsonizeMe;
192     @jsonize int i;
193     @jsonize string[] a;
194   }
195 
196   auto foo = Foo(12, [ "a", "b" ]);
197   assert(foo.toJSON() == `{"i": 12, "a": [ "a", "b" ]}`.parseJSON);
198 }
199 
200 /// Whether to nicely format json string.
201 alias PrettyJson = Flag!"PrettyJson";
202 
203 /// Convert an instance of some type `T` directly into a json-formatted string.
204 /// Params:
205 ///   T      = type of object to convert
206 ///   obj    = object to convert to sjon
207 ///   pretty = whether to prettify string output
208 string toJSONString(T)(T obj, PrettyJson pretty = PrettyJson.yes) {
209   auto json = obj.toJSON!T;
210   return pretty ? json.toPrettyString : json.toString;
211 }
212 
213 unittest {
214   assert([1, 2, 3].toJSONString(PrettyJson.no) == "[1,2,3]");
215   assert([1, 2, 3].toJSONString(PrettyJson.yes) == "[\n    1,\n    2,\n    3\n]");
216 }
217 
218 /// Write a jsonizeable object to a file.
219 /// Params:
220 ///   path = filesystem path to write json to
221 ///   obj  = object to convert to json and write to path
222 void writeJSON(T)(string path, T obj) {
223   auto json = toJSON!T(obj);
224   path.write(json.toPrettyString);
225 }
226 
227 unittest {
228   import std.path : buildPath;
229   import std.uuid : randomUUID;
230 
231   auto dir = buildPath(tempDir(), "jsonizer_writejson_test");
232   mkdirRecurse(dir);
233   auto file = buildPath(dir, randomUUID().toString);
234 
235   file.writeJSON([1, 2, 3]);
236 
237   auto json = file.readText.parseJSON;
238   assert(json.array[0].integer == 1);
239   assert(json.array[1].integer == 2);
240   assert(json.array[2].integer == 3);
241 }
242 
243 // True if `val` is equal to the initial value of that type
244 bool isInitial(T)(T val)
245 {
246   static if (is(typeof(val == T.init)))
247     return val == T.init;
248   else static if (is(typeof(val is T.init)))
249     return val is T.init;
250   else static assert(0, "isInitial can't handle " ~ T.stringof);
251 }