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 }