1 module painlessjson; 2 3 import std.conv; 4 import std.json; 5 import std.range; 6 import std.traits; 7 8 version(unittest) 9 { 10 import std.algorithm; 11 import std.stdio; 12 13 struct Point 14 { 15 double x = 0; 16 double y = 1; 17 this(double x_, double y_) 18 { 19 x = x_; 20 y = y_; 21 } 22 23 string foo() 24 { 25 writeln("Functions should not be called"); 26 return "Noooooo!"; 27 } 28 29 static string bar() 30 { 31 writeln("Static functions should not be called"); 32 return "Noooooo!"; 33 } 34 35 } 36 37 class PointC 38 { 39 double x = 0; 40 double y = 1; 41 this() 42 { 43 } 44 45 ; 46 this(double x_, double y_) 47 { 48 x = x_; 49 y = y_; 50 } 51 52 string foo() 53 { 54 writeln("Class functions should not be called"); 55 return "Noooooo!"; 56 } 57 58 } 59 60 class PointPrivate 61 { 62 private double _x; 63 private double _y; 64 this(double x_, double y_) 65 { 66 _x = x_; 67 _y = y_; 68 } 69 70 string foo() 71 { 72 writeln("Class functions should not be called"); 73 return "Noooooo!"; 74 } 75 76 double x() 77 { 78 return _x; 79 } 80 81 double y() 82 { 83 return _y; 84 } 85 86 static PointPrivate _fromJSON(JSONValue value) 87 { 88 return new PointPrivate(fromJSON!double(value["x"]), fromJSON!double(value["y"])); 89 } 90 91 JSONValue _toJSON() 92 { 93 JSONValue[string] json; 94 json["x"] = JSONValue(x); 95 json["y"] = JSONValue(y); 96 return JSONValue(json); 97 } 98 99 } 100 101 } 102 103 /// Template function that converts any object to JSON 104 JSONValue toJSON(T)(T object) 105 { 106 static if (__traits(compiles, (T t) 107 { 108 JSONValue(t); 109 } 110 )) 111 { 112 return JSONValue(object); 113 } 114 else static if (isArray!T) 115 { 116 // Range 117 JSONValue[] jsonRange; 118 jsonRange = map!((el) => el.toJSON)(object).array; 119 return JSONValue(jsonRange); 120 } 121 else static if (isAssociativeArray!T) 122 { 123 // Range 124 JSONValue[string] jsonAA; 125 foreach (key, value; object) 126 { 127 jsonAA[key.toJSON.toString] = value.toJSON; 128 } 129 return JSONValue(jsonAA); 130 } 131 else static if (__traits(compiles, (T t) 132 { 133 return object._toJSON(); 134 } 135 )) 136 { 137 return object._toJSON(); 138 } 139 else 140 { 141 JSONValue[string] json; 142 // Getting all member variables (there is probably an easier way) 143 foreach (name; __traits(allMembers, T)) 144 { 145 static if (__traits(compiles, __traits(getMember, object, name) 146 .toJSON) 147 //&& __traits(compiles, __traits(getMember, object, name)) 148 // Skip Functions 149 && !isSomeFunction!(__traits(getMember, object, name))) 150 { 151 // Can we get a value? (filters out void * this) 152 json[name] = __traits(getMember, object, name).toJSON; 153 } 154 } 155 return JSONValue(json); 156 } 157 } 158 159 160 /// Converting common types 161 unittest 162 { 163 assert(5.toJSON!int == JSONValue(5)); 164 assert(4.toJSON != JSONValue(5)); 165 assert(5.4.toJSON == JSONValue(5.4)); 166 assert(toJSON("test") == JSONValue("test")); 167 assert(toJSON(JSONValue("test")) == JSONValue("test")); 168 } 169 170 171 /// Converting InputRanges 172 unittest 173 { 174 assert([1, 2].toJSON.toString == "[1,2]"); 175 } 176 177 178 /// User structs 179 unittest 180 { 181 Point p; 182 assert(toJSON(p).toString == q{{"x":0,"y":1}}); 183 } 184 185 186 /// Array of structs 187 unittest 188 { 189 Point[] ps = [Point(-1, 1), Point(2, 3)]; 190 assert(toJSON(ps).toString == q{[{"x":-1,"y":1},{"x":2,"y":3}]}); 191 } 192 193 194 /// User class 195 unittest 196 { 197 PointC p = new PointC(1, -2); 198 assert(toJSON(p).toString == q{{"x":1,"y":-2}}); 199 } 200 201 202 /// User class with private fields 203 unittest 204 { 205 PointPrivate p = new PointPrivate(-1, 2); 206 assert(toJSON(p).toString == q{{"x":-1,"y":2}}); 207 } 208 209 210 /// Array of classes 211 unittest 212 { 213 PointC[] ps = [new PointC(-1, 1), new PointC(2, 3)]; 214 assert(toJSON(ps).toString == q{[{"x":-1,"y":1},{"x":2,"y":3}]}); 215 } 216 217 218 /// Associative array 219 unittest 220 { 221 string[int] aa = [0 : "a", 1 : "b"]; 222 // In JSON (D) only string based associative arrays are supported, so: 223 assert(aa.toJSON.toString == q{{"0":"a","1":"b"}}); 224 Point[int] aaStruct = [0 : Point(-1, 1), 1 : Point(2, 0)]; 225 assert(aaStruct.toJSON.toString == q{{"0":{"x":-1,"y":1},"1":{"x":2,"y":0}}}); 226 } 227 228 229 /// Overloaded toJSON 230 unittest 231 { 232 class A 233 { 234 double x = 0; 235 double y = 1; 236 JSONValue toJSON() 237 { 238 JSONValue[string] json; 239 json["x"] = x; 240 return JSONValue(json); 241 } 242 243 } 244 245 auto a = new A; 246 assert(a.toJSON.toString == q{{"x":0}}); 247 248 class B 249 { 250 double x = 0; 251 double y = 1; 252 } 253 254 255 // Both templates will now work for B, so this is ambiguous in D. 256 // Under dmd it looks like the toJSON!T that is loaded first is the one used 257 JSONValue toJSON(T : B)(T b) 258 { 259 JSONValue[string] json; 260 json["x"] = b.x; 261 return JSONValue(json); 262 } 263 264 auto b = new B; 265 assert(b.toJSON.toString == q{{"x":0,"y":1}}); 266 267 class Z 268 { 269 double x = 0; 270 double y = 1; 271 // Adding an extra value 272 JSONValue toJSON() 273 { 274 JSONValue[string] json = painlessjson.toJSON!Z(this).object; 275 json["add"] = "bla".toJSON; 276 return JSONValue(json); 277 } 278 279 } 280 281 auto z = new Z; 282 assert(z.toJSON.toString == q{{"x":0,"y":1,"add":"bla"}}); 283 } 284 285 286 /// Convert from JSONValue to any other type 287 T fromJSON(T)(JSONValue json) 288 { 289 static if (is(T == JSONValue)) 290 { 291 return json; 292 } 293 else static if (isIntegral!T) 294 { 295 return to!T(json.integer); 296 } 297 else static if (isFloatingPoint!T) 298 { 299 if (json.type == JSON_TYPE.INTEGER) 300 return to!T(json.integer); 301 else return to!T(json.floating); 302 } 303 else static if (is(T == string)) 304 { 305 return to!T(json.str); 306 } 307 else static if (isBoolean!T) 308 { 309 if (json.type == JSON_TYPE.TRUE) 310 return true; 311 else return false; 312 } 313 else static if (__traits(compiles, 314 { 315 return T._fromJSON(json); 316 } 317 )) 318 { 319 return T._fromJSON(json); 320 } 321 else 322 { 323 T t; 324 static if (__traits(compiles, cast(Object)(t)) && __traits(compiles, 325 new T)) 326 { 327 t = new T; 328 } 329 static if (isArray!T) 330 { 331 t = map!((js) => fromJSON!(typeof(t.front))(js))(json.array).array; 332 } 333 else static if (isAssociativeArray!T) 334 { 335 JSONValue[string] jsonAA = json.object; 336 foreach (k, v; jsonAA) 337 { 338 t[fromJSON!(typeof(t.keys.front))(parseJSON(k))] = fromJSON!(typeof(t 339 .values.front))(v); 340 } 341 } 342 else 343 { 344 mixin ("JSONValue[string] jsonAA = json.object;"); 345 foreach (name; __traits(allMembers, T)) 346 { 347 static if (__traits(compiles, __traits(getMember, t, name)) 348 && __traits(compiles, typeof(__traits(getMember, t, name))) 349 // Skip Functions 350 && !isSomeFunction!(__traits(getMember, t, name))) 351 { 352 // Can we get a value? (filters out void * this) 353 mixin ("if ( \"" ~ name ~ "\" in jsonAA) t." ~ name 354 ~ "= fromJSON!(" ~ (typeof(__traits(getMember, t, name))) 355 .stringof ~ ")(jsonAA[\"" ~ name ~ "\"]);"); 356 } 357 } 358 } 359 return t; 360 } 361 } 362 363 364 /// Converting common types 365 unittest 366 { 367 assert(fromJSON!int(JSONValue(1)) == 1); 368 assert(fromJSON!double(JSONValue(1.0)) == 1); 369 assert(fromJSON!double(JSONValue(1.3)) == 1.3); 370 assert(fromJSON!string(JSONValue("str")) == "str"); 371 assert(fromJSON!bool(JSONValue(true)) == true); 372 assert(fromJSON!bool(JSONValue(false)) == false); 373 assert(fromJSON!JSONValue(JSONValue(true)) == JSONValue(true)); 374 } 375 376 377 /// Converting arrays 378 unittest 379 { 380 assert(equal(fromJSON!(int[])(toJSON([1, 2])), [1, 2])); 381 } 382 383 384 /// Associative arrays 385 unittest 386 { 387 string[int] aa = [0 : "a", 1 : "b"]; 388 auto aaCpy = fromJSON!(string[int])(toJSON(aa)); 389 foreach (k, v; aa) 390 { 391 assert(aaCpy[k] == v); 392 } 393 } 394 395 396 /// Structs from JSON 397 unittest 398 { 399 auto p = fromJSON!Point(parseJSON(q{{"x":-1,"y":2}})); 400 assert(p.x == -1); 401 assert(p.y == 2); 402 p = fromJSON!Point(parseJSON(q{{"x":2}})); 403 assert(p.x == 2); 404 assert(p.y == 1); 405 p = fromJSON!Point(parseJSON(q{{"y":3}})); 406 assert(p.x == 0); 407 assert(p.y == 3); 408 p = fromJSON!Point(parseJSON(q{{"x":-1,"y":2,"z":3}})); 409 assert(p.x == -1); 410 assert(p.y == 2); 411 } 412 413 414 /// Class from JSON 415 unittest 416 { 417 auto p = fromJSON!PointC(parseJSON(q{{"x":-1,"y":2}})); 418 assert(p.x == -1); 419 assert(p.y == 2); 420 } 421 422 423 /** 424 Convert class from JSON using "_fromJSON" 425 */ 426 unittest 427 { 428 auto p = fromJSON!PointPrivate(parseJSON(q{{"x":-1,"y":2}})); 429 assert(p.x == -1); 430 assert(p.y == 2); 431 }