1 /** 2 JSON serialization and value handling. 3 4 This module provides the Json struct for reading, writing and manipulating JSON values in a seamless, 5 JavaScript like way. De(serialization) of arbitrary D types is also supported. 6 7 Examples: 8 9 --- 10 void manipulateJson(Json j) 11 { 12 // object members can be accessed using member syntax, just like in JavaScript 13 j = Json.EmptyObject; 14 j.name = "Example"; 15 j.id = 1; 16 17 // retrieving the values is done using get() 18 assert(j["name"].get!string == "Example"); 19 assert(j["id"].get!int == 1); 20 21 // semantic convertions can be done using to() 22 assert(j.id.to!string == "1"); 23 24 // prints: 25 // name: "Example" 26 // id: 1 27 foreach( string key, value; j ){ 28 writefln("%s: %s", key, value); 29 } 30 31 // print out as JSON: {"name": "Example", "id": 1} 32 writefln("JSON: %s", j.toString()); 33 } 34 --- 35 36 Copyright: © 2012 RejectedSoftware e.K. 37 License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 38 Authors: Sönke Ludwig 39 */ 40 module dub.internal.vibecompat.data.json; 41 42 import dub.internal.vibecompat.data.utils; 43 44 import std.array; 45 import std.conv; 46 import std.datetime; 47 import std.exception; 48 import std.format; 49 import std.string; 50 import std.range; 51 import std.traits; 52 53 54 /******************************************************************************/ 55 /* public types */ 56 /******************************************************************************/ 57 58 /** 59 Represents a single JSON value. 60 61 Json values can have one of the types defined in the Json.Type enum. They 62 behave mostly like values in ECMA script in the way that you can 63 transparently perform operations on them. However, strict typechecking is 64 done, so that operations between differently typed JSON values will throw 65 an exception. Additionally, an explicit cast or using get!() or to!() is 66 required to convert a JSON value to the corresponding static D type. 67 */ 68 struct Json { 69 private { 70 union { 71 bool m_bool; 72 long m_int; 73 double m_float; 74 string m_string; 75 Json[] m_array; 76 Json[string] m_object; 77 }; 78 Type m_type = Type.Undefined; 79 } 80 81 /** Represents the run time type of a JSON value. 82 */ 83 enum Type { 84 /// A non-existent value in a JSON object 85 Undefined, 86 /// Null value 87 Null, 88 /// Boolean value 89 Bool, 90 /// 64-bit integer value 91 Int, 92 /// 64-bit floating point value 93 Float, 94 /// UTF-8 string 95 String, 96 /// Array of JSON values 97 Array, 98 /// JSON object aka. dictionary from string to Json 99 Object 100 } 101 102 /// New JSON value of Type.Undefined 103 static @property Json Undefined() { return Json(); } 104 105 /// New JSON value of Type.Object 106 static @property Json EmptyObject() { return Json(cast(Json[string])null); } 107 108 /// New JSON value of Type.Array 109 static @property Json EmptyArray() { return Json(cast(Json[])null); } 110 111 version(JsonLineNumbers) int line; 112 113 /** 114 Constructor for a JSON object. 115 */ 116 this(typeof(null)) { m_type = Type.Null; } 117 /// ditto 118 this(bool v) { m_type = Type.Bool; m_bool = v; } 119 /// ditto 120 this(int v) { m_type = Type.Int; m_int = v; } 121 /// ditto 122 this(long v) { m_type = Type.Int; m_int = v; } 123 /// ditto 124 this(double v) { m_type = Type.Float; m_float = v; } 125 /// ditto 126 this(string v) { m_type = Type.String; m_string = v; } 127 /// ditto 128 this(Json[] v) { m_type = Type.Array; m_array = v; } 129 /// ditto 130 this(Json[string] v) { m_type = Type.Object; m_object = v; } 131 132 /** 133 Allows assignment of D values to a JSON value. 134 */ 135 ref Json opAssign(Json v){ 136 m_type = v.m_type; 137 final switch(m_type){ 138 case Type.Undefined: m_string = null; break; 139 case Type.Null: m_string = null; break; 140 case Type.Bool: m_bool = v.m_bool; break; 141 case Type.Int: m_int = v.m_int; break; 142 case Type.Float: m_float = v.m_float; break; 143 case Type.String: m_string = v.m_string; break; 144 case Type.Array: m_array = v.m_array; break; 145 case Type.Object: m_object = v.m_object; break; 146 } 147 return this; 148 } 149 /// ditto 150 void opAssign(typeof(null)) { m_type = Type.Null; m_string = null; } 151 /// ditto 152 bool opAssign(bool v) { m_type = Type.Bool; m_bool = v; return v; } 153 /// ditto 154 int opAssign(int v) { m_type = Type.Int; m_int = v; return v; } 155 /// ditto 156 long opAssign(long v) { m_type = Type.Int; m_int = v; return v; } 157 /// ditto 158 double opAssign(double v) { m_type = Type.Float; m_float = v; return v; } 159 /// ditto 160 string opAssign(string v) { m_type = Type.String; m_string = v; return v; } 161 /// ditto 162 Json[] opAssign(Json[] v) { m_type = Type.Array; m_array = v; return v; } 163 /// ditto 164 Json[string] opAssign(Json[string] v) { m_type = Type.Object; m_object = v; return v; } 165 166 /** 167 The current type id of this JSON object. 168 */ 169 @property Type type() const { return m_type; } 170 171 /** 172 Allows direct indexing of array typed JSON values. 173 */ 174 ref inout(Json) opIndex(size_t idx) inout { checkType!(Json[])(); return m_array[idx]; } 175 176 /** 177 Allows direct indexing of object typed JSON values using a string as 178 the key. 179 */ 180 const(Json) opIndex(string key) const { 181 checkType!(Json[string])(); 182 if( auto pv = key in m_object ) return *pv; 183 Json ret = Json.Undefined; 184 ret.m_string = key; 185 return ret; 186 } 187 /// ditto 188 ref Json opIndex(string key){ 189 checkType!(Json[string])(); 190 if( auto pv = key in m_object ) 191 return *pv; 192 m_object[key] = Json(); 193 m_object[key].m_type = Type.Undefined; // DMDBUG: AAs are teh $H1T!!!11 194 assert(m_object[key].type == Type.Undefined); 195 m_object[key].m_string = key; 196 return m_object[key]; 197 } 198 199 /** 200 Returns a slice of a JSON array. 201 */ 202 inout(Json[]) opSlice() inout { checkType!(Json[])(); return m_array; } 203 /// 204 inout(Json[]) opSlice(size_t from, size_t to) inout { checkType!(Json[])(); return m_array[from .. to]; } 205 206 /** 207 Removes an entry from an object. 208 */ 209 void remove(string item) { checkType!(Json[string])(); m_object.remove(item); } 210 211 /** 212 Returns the number of entries of string, array or object typed JSON values. 213 */ 214 @property size_t length() 215 const { 216 switch(m_type){ 217 case Type.String: return m_string.length; 218 case Type.Array: return m_array.length; 219 case Type.Object: return m_object.length; 220 default: 221 enforce(false, "Json.length() can only be called on strings, arrays and objects, not "~.to!string(m_type)~"."); 222 return 0; 223 } 224 } 225 226 /** 227 Allows foreach iterating over JSON objects and arrays. 228 */ 229 int opApply(int delegate(ref Json obj) del) 230 { 231 enforce(m_type == Type.Array || m_type == Type.Object, "opApply may only be called on objects and arrays, not "~.to!string(m_type)~"."); 232 if( m_type == Type.Array ){ 233 foreach( ref v; m_array ) 234 if( auto ret = del(v) ) 235 return ret; 236 return 0; 237 } else { 238 foreach( ref v; m_object ) 239 if( v.type != Type.Undefined ) 240 if( auto ret = del(v) ) 241 return ret; 242 return 0; 243 } 244 } 245 /// ditto 246 int opApply(int delegate(ref const Json obj) del) 247 const { 248 enforce(m_type == Type.Array || m_type == Type.Object, "opApply may only be called on objects and arrays, not "~.to!string(m_type)~"."); 249 if( m_type == Type.Array ){ 250 foreach( ref v; m_array ) 251 if( auto ret = del(v) ) 252 return ret; 253 return 0; 254 } else { 255 foreach( ref v; m_object ) 256 if( v.type != Type.Undefined ) 257 if( auto ret = del(v) ) 258 return ret; 259 return 0; 260 } 261 } 262 /// ditto 263 int opApply(int delegate(ref size_t idx, ref Json obj) del) 264 { 265 enforce(m_type == Type.Array, "opApply may only be called on arrays, not "~.to!string(m_type)~""); 266 foreach( idx, ref v; m_array ) 267 if( auto ret = del(idx, v) ) 268 return ret; 269 return 0; 270 } 271 /// ditto 272 int opApply(int delegate(ref size_t idx, ref const Json obj) del) 273 const { 274 enforce(m_type == Type.Array, "opApply may only be called on arrays, not "~.to!string(m_type)~"."); 275 foreach( idx, ref v; m_array ) 276 if( auto ret = del(idx, v) ) 277 return ret; 278 return 0; 279 } 280 /// ditto 281 int opApply(int delegate(ref string idx, ref Json obj) del) 282 { 283 enforce(m_type == Type.Object, "opApply may only be called on objects, not "~.to!string(m_type)~"."); 284 foreach( idx, ref v; m_object ) 285 if( v.type != Type.Undefined ) 286 if( auto ret = del(idx, v) ) 287 return ret; 288 return 0; 289 } 290 /// ditto 291 int opApply(int delegate(ref string idx, ref const Json obj) del) 292 const { 293 enforce(m_type == Type.Object, "opApply may only be called on objects, not "~.to!string(m_type)~"."); 294 foreach( idx, ref v; m_object ) 295 if( v.type != Type.Undefined ) 296 if( auto ret = del(idx, v) ) 297 return ret; 298 return 0; 299 } 300 301 /** 302 Converts the JSON value to the corresponding D type - types must match exactly. 303 */ 304 inout(T) opCast(T)() inout { return get!T; } 305 /// ditto 306 @property inout(T) get(T)() 307 inout { 308 checkType!T(); 309 static if( is(T == bool) ) return m_bool; 310 else static if( is(T == double) ) return m_float; 311 else static if( is(T == float) ) return cast(T)m_float; 312 else static if( is(T == long) ) return m_int; 313 else static if( is(T : long) ){ enforce(m_int <= T.max && m_int >= T.min); return cast(T)m_int; } 314 else static if( is(T == string) ) return m_string; 315 else static if( is(T == Json[]) ) return m_array; 316 else static if( is(T == Json[string]) ) return m_object; 317 else static assert("JSON can only be casted to (bool, long, double, string, Json[] or Json[string]. Not "~T.stringof~"."); 318 } 319 /// ditto 320 @property const(T) opt(T)(const(T) def = T.init) 321 const { 322 if( typeId!T != m_type ) return def; 323 return get!T; 324 } 325 /// ditto 326 @property T opt(T)(T def = T.init) 327 { 328 if( typeId!T != m_type ) return def; 329 return get!T; 330 } 331 332 /** 333 Converts the JSON value to the corresponding D type - types are converted as neccessary. 334 */ 335 @property inout(T) to(T)() 336 inout { 337 static if( is(T == bool) ){ 338 final switch( m_type ){ 339 case Type.Undefined: return false; 340 case Type.Null: return false; 341 case Type.Bool: return m_bool; 342 case Type.Int: return m_int != 0; 343 case Type.Float: return m_float != 0; 344 case Type.String: return m_string.length > 0; 345 case Type.Array: return m_array.length > 0; 346 case Type.Object: return m_object.length > 0; 347 } 348 } else static if( is(T == double) ){ 349 final switch( m_type ){ 350 case Type.Undefined: return T.init; 351 case Type.Null: return 0; 352 case Type.Bool: return m_bool ? 1 : 0; 353 case Type.Int: return m_int; 354 case Type.Float: return m_float; 355 case Type.String: return .to!double(cast(string)m_string); 356 case Type.Array: return double.init; 357 case Type.Object: return double.init; 358 } 359 } else static if( is(T == float) ){ 360 final switch( m_type ){ 361 case Type.Undefined: return T.init; 362 case Type.Null: return 0; 363 case Type.Bool: return m_bool ? 1 : 0; 364 case Type.Int: return m_int; 365 case Type.Float: return m_float; 366 case Type.String: return .to!float(cast(string)m_string); 367 case Type.Array: return float.init; 368 case Type.Object: return float.init; 369 } 370 } 371 else static if( is(T == long) ){ 372 final switch( m_type ){ 373 case Type.Undefined: return 0; 374 case Type.Null: return 0; 375 case Type.Bool: return m_bool ? 1 : 0; 376 case Type.Int: return m_int; 377 case Type.Float: return cast(long)m_float; 378 case Type.String: return .to!long(m_string); 379 case Type.Array: return 0; 380 case Type.Object: return 0; 381 } 382 } else static if( is(T : long) ){ 383 final switch( m_type ){ 384 case Type.Undefined: return 0; 385 case Type.Null: return 0; 386 case Type.Bool: return m_bool ? 1 : 0; 387 case Type.Int: return cast(T)m_int; 388 case Type.Float: return cast(T)m_float; 389 case Type.String: return cast(T).to!long(cast(string)m_string); 390 case Type.Array: return 0; 391 case Type.Object: return 0; 392 } 393 } else static if( is(T == string) ){ 394 switch( m_type ){ 395 default: return toString(); 396 case Type.String: return m_string; 397 } 398 } else static if( is(T == Json[]) ){ 399 switch( m_type ){ 400 default: return Json([this]); 401 case Type.Array: return m_array; 402 } 403 } else static if( is(T == Json[string]) ){ 404 switch( m_type ){ 405 default: return Json(["value": this]); 406 case Type.Object: return m_object; 407 } 408 } else static assert("JSON can only be casted to (bool, long, double, string, Json[] or Json[string]. Not "~T.stringof~"."); 409 } 410 411 /** 412 Performs unary operations on the JSON value. 413 414 The following operations are supported for each type: 415 416 $(DL 417 $(DT Null) $(DD none) 418 $(DT Bool) $(DD ~) 419 $(DT Int) $(DD +, -, ++, --) 420 $(DT Float) $(DD +, -, ++, --) 421 $(DT String) $(DD none) 422 $(DT Array) $(DD none) 423 $(DT Object) $(DD none) 424 ) 425 */ 426 Json opUnary(string op)() 427 const { 428 static if( op == "~" ){ 429 checkType!bool(); 430 return Json(~m_bool); 431 } else static if( op == "+" || op == "-" || op == "++" || op == "--" ){ 432 if( m_type == Type.Int ) mixin("return Json("~op~"m_int);"); 433 else if( m_type == Type.Float ) mixin("return Json("~op~"m_float);"); 434 else enforce(false, "'"~op~"' only allowed on scalar types, not on "~.to!string(m_type)~"."); 435 } else static assert("Unsupported operator '"~op~"' for type JSON."); 436 } 437 438 /** 439 Performs binary operations between JSON values. 440 441 The two JSON values must be of the same run time type or an exception 442 will be thrown. Only the operations listed are allowed for each of the 443 types. 444 445 $(DL 446 $(DT Null) $(DD none) 447 $(DT Bool) $(DD &&, ||) 448 $(DT Int) $(DD +, -, *, /, %) 449 $(DT Float) $(DD +, -, *, /, %) 450 $(DT String) $(DD ~) 451 $(DT Array) $(DD ~) 452 $(DT Object) $(DD none) 453 ) 454 */ 455 Json opBinary(string op)(ref const(Json) other) 456 const { 457 enforce(m_type == other.m_type, "Binary operation '"~op~"' between "~.to!string(m_type)~" and "~.to!string(other.m_type)~" JSON objects."); 458 static if( op == "&&" ){ 459 enforce(m_type == Type.Bool, "'&&' only allowed for Type.Bool, not "~.to!string(m_type)~"."); 460 return Json(m_bool && other.m_bool); 461 } else static if( op == "||" ){ 462 enforce(m_type == Type.Bool, "'||' only allowed for Type.Bool, not "~.to!string(m_type)~"."); 463 return Json(m_bool || other.m_bool); 464 } else static if( op == "+" ){ 465 if( m_type == Type.Int ) return Json(m_int + other.m_int); 466 else if( m_type == Type.Float ) return Json(m_float + other.m_float); 467 else enforce(false, "'+' only allowed for scalar types, not "~.to!string(m_type)~"."); 468 } else static if( op == "-" ){ 469 if( m_type == Type.Int ) return Json(m_int - other.m_int); 470 else if( m_type == Type.Float ) return Json(m_float - other.m_float); 471 else enforce(false, "'-' only allowed for scalar types, not "~.to!string(m_type)~"."); 472 } else static if( op == "*" ){ 473 if( m_type == Type.Int ) return Json(m_int * other.m_int); 474 else if( m_type == Type.Float ) return Json(m_float * other.m_float); 475 else enforce(false, "'*' only allowed for scalar types, not "~.to!string(m_type)~"."); 476 } else static if( op == "/" ){ 477 if( m_type == Type.Int ) return Json(m_int / other.m_int); 478 else if( m_type == Type.Float ) return Json(m_float / other.m_float); 479 else enforce(false, "'/' only allowed for scalar types, not "~.to!string(m_type)~"."); 480 } else static if( op == "%" ){ 481 if( m_type == Type.Int ) return Json(m_int % other.m_int); 482 else if( m_type == Type.Float ) return Json(m_float % other.m_float); 483 else enforce(false, "'%' only allowed for scalar types, not "~.to!string(m_type)~"."); 484 } else static if( op == "~" ){ 485 if( m_type == Type.String ) return Json(m_string ~ other.m_string); 486 else enforce(false, "'~' only allowed for strings, not "~.to!string(m_type)~"."); 487 } else static assert("Unsupported operator '"~op~"' for type JSON."); 488 assert(false); 489 } 490 /// ditto 491 Json opBinary(string op)(Json other) 492 if( op == "~" ) 493 { 494 static if( op == "~" ){ 495 if( m_type == Type.String ) return Json(m_string ~ other.m_string); 496 else if( m_type == Type.Array ) return Json(m_array ~ other.m_array); 497 else enforce(false, "'~' only allowed for strings and arrays, not "~.to!string(m_type)~"."); 498 } else static assert("Unsupported operator '"~op~"' for type JSON."); 499 assert(false); 500 } 501 /// ditto 502 void opOpAssign(string op)(Json other) 503 if( op == "+" || op == "-" || op == "*" ||op == "/" || op == "%" ) 504 { 505 enforce(m_type == other.m_type, "Binary operation '"~op~"' between "~.to!string(m_type)~" and "~.to!string(other.m_type)~" JSON objects."); 506 static if( op == "+" ){ 507 if( m_type == Type.Int ) m_int += other.m_int; 508 else if( m_type == Type.Float ) m_float += other.m_float; 509 else enforce(false, "'+' only allowed for scalar types, not "~.to!string(m_type)~"."); 510 } else static if( op == "-" ){ 511 if( m_type == Type.Int ) m_int -= other.m_int; 512 else if( m_type == Type.Float ) m_float -= other.m_float; 513 else enforce(false, "'-' only allowed for scalar types, not "~.to!string(m_type)~"."); 514 } else static if( op == "*" ){ 515 if( m_type == Type.Int ) m_int *= other.m_int; 516 else if( m_type == Type.Float ) m_float *= other.m_float; 517 else enforce(false, "'*' only allowed for scalar types, not "~.to!string(m_type)~"."); 518 } else static if( op == "/" ){ 519 if( m_type == Type.Int ) m_int /= other.m_int; 520 else if( m_type == Type.Float ) m_float /= other.m_float; 521 else enforce(false, "'/' only allowed for scalar types, not "~.to!string(m_type)~"."); 522 } else static if( op == "%" ){ 523 if( m_type == Type.Int ) m_int %= other.m_int; 524 else if( m_type == Type.Float ) m_float %= other.m_float; 525 else enforce(false, "'%' only allowed for scalar types, not "~.to!string(m_type)~"."); 526 } /*else static if( op == "~" ){ 527 if( m_type == Type.String ) m_string ~= other.m_string; 528 else if( m_type == Type.Array ) m_array ~= other.m_array; 529 else enforce(false, "'%' only allowed for scalar types, not "~.to!string(m_type)~"."); 530 }*/ else static assert("Unsupported operator '"~op~"' for type JSON."); 531 assert(false); 532 } 533 /// ditto 534 Json opBinary(string op)(bool other) const { checkType!bool(); mixin("return Json(m_bool "~op~" other);"); } 535 /// ditto 536 Json opBinary(string op)(long other) const { checkType!long(); mixin("return Json(m_int "~op~" other);"); } 537 /// ditto 538 Json opBinary(string op)(double other) const { checkType!double(); mixin("return Json(m_float "~op~" other);"); } 539 /// ditto 540 Json opBinary(string op)(string other) const { checkType!string(); mixin("return Json(m_string "~op~" other);"); } 541 /// ditto 542 Json opBinary(string op)(Json other) 543 if (op == "~") { 544 if (m_type == Type.Array) return Json(m_array ~ other); 545 else return Json(this ~ other); 546 } 547 /// ditto 548 Json opBinaryRight(string op)(bool other) const { checkType!bool(); mixin("return Json(other "~op~" m_bool);"); } 549 /// ditto 550 Json opBinaryRight(string op)(long other) const { checkType!long(); mixin("return Json(other "~op~" m_int);"); } 551 /// ditto 552 Json opBinaryRight(string op)(double other) const { checkType!double(); mixin("return Json(other "~op~" m_float);"); } 553 /// ditto 554 Json opBinaryRight(string op)(string other) const if(op == "~") { checkType!string(); return Json(other ~ m_string); } 555 /// ditto 556 inout(Json)* opBinaryRight(string op)(string other) inout if(op == "in") { 557 checkType!(Json[string])(); 558 auto pv = other in m_object; 559 if( !pv ) return null; 560 if( pv.type == Type.Undefined ) return null; 561 return pv; 562 } 563 564 /** 565 Allows to access existing fields of a JSON object using dot syntax. 566 */ 567 @property const(Json) opDispatch(string prop)() const { return opIndex(prop); } 568 /// ditto 569 @property ref Json opDispatch(string prop)() { return opIndex(prop); } 570 571 /** 572 Compares two JSON values for equality. 573 574 If the two values have different types, they are considered unequal. 575 This differs with ECMA script, which performs a type conversion before 576 comparing the values. 577 */ 578 bool opEquals(ref const Json other) 579 const { 580 if( m_type != other.m_type ) return false; 581 final switch(m_type){ 582 case Type.Undefined: return false; 583 case Type.Null: return true; 584 case Type.Bool: return m_bool == other.m_bool; 585 case Type.Int: return m_int == other.m_int; 586 case Type.Float: return m_float == other.m_float; 587 case Type.String: return m_string == other.m_string; 588 case Type.Array: return m_array == other.m_array; 589 case Type.Object: return m_object == other.m_object; 590 } 591 } 592 /// ditto 593 bool opEquals(const Json other) const { return opEquals(other); } 594 /// ditto 595 bool opEquals(typeof(null)) const { return m_type == Type.Null; } 596 /// ditto 597 bool opEquals(bool v) const { return m_type == Type.Bool && m_bool == v; } 598 /// ditto 599 bool opEquals(long v) const { return m_type == Type.Int && m_int == v; } 600 /// ditto 601 bool opEquals(double v) const { return m_type == Type.Float && m_float == v; } 602 /// ditto 603 bool opEquals(string v) const { return m_type == Type.String && m_string == v; } 604 605 /** 606 Compares two JSON values. 607 608 If the types of the two values differ, the value with the smaller type 609 id is considered the smaller value. This differs from ECMA script, which 610 performs a type conversion before comparing the values. 611 612 JSON values of type Object cannot be compared and will throw an 613 exception. 614 */ 615 int opCmp(ref const Json other) 616 const { 617 if( m_type != other.m_type ) return m_type < other.m_type ? -1 : 1; 618 final switch(m_type){ 619 case Type.Undefined: return 0; 620 case Type.Null: return 0; 621 case Type.Bool: return m_bool < other.m_bool ? -1 : m_bool == other.m_bool ? 0 : 1; 622 case Type.Int: return m_int < other.m_int ? -1 : m_int == other.m_int ? 0 : 1; 623 case Type.Float: return m_float < other.m_float ? -1 : m_float == other.m_float ? 0 : 1; 624 case Type.String: return m_string < other.m_string ? -1 : m_string == other.m_string ? 0 : 1; 625 case Type.Array: return m_array < other.m_array ? -1 : m_array == other.m_array ? 0 : 1; 626 case Type.Object: 627 enforce(false, "JSON objects cannot be compared."); 628 assert(false); 629 } 630 } 631 632 633 634 /** 635 Returns the type id corresponding to the given D type. 636 */ 637 static @property Type typeId(T)() { 638 static if( is(T == typeof(null)) ) return Type.Null; 639 else static if( is(T == bool) ) return Type.Bool; 640 else static if( is(T == double) ) return Type.Float; 641 else static if( is(T == float) ) return Type.Float; 642 else static if( is(T : long) ) return Type.Int; 643 else static if( is(T == string) ) return Type.String; 644 else static if( is(T == Json[]) ) return Type.Array; 645 else static if( is(T == Json[string]) ) return Type.Object; 646 else static assert(false, "Unsupported JSON type '"~T.stringof~"'. Only bool, long, double, string, Json[] and Json[string] are allowed."); 647 } 648 649 /** 650 Returns the JSON object as a string. 651 652 For large JSON values use writeJsonString instead as this function will store the whole string 653 in memory, whereas writeJsonString writes it out bit for bit. 654 655 See_Also: writeJsonString, toPrettyString 656 */ 657 string toString() 658 const { 659 auto ret = appender!string(); 660 writeJsonString(ret, this); 661 return ret.data; 662 } 663 664 /** 665 Returns the JSON object as a "pretty" string. 666 667 --- 668 auto json = Json(["foo": Json("bar")]); 669 writeln(json.toPrettyString()); 670 671 // output: 672 // { 673 // "foo": "bar" 674 // } 675 --- 676 677 Params: 678 level = Specifies the base amount of indentation for the output. Indentation is always 679 done using tab characters. 680 681 See_Also: writePrettyJsonString, toString 682 */ 683 string toPrettyString(int level = 0) 684 const { 685 auto ret = appender!string(); 686 writePrettyJsonString(ret, this, level); 687 return ret.data; 688 } 689 690 private void checkType(T)() 691 const { 692 string dbg; 693 if( m_type == Type.Undefined ) dbg = " field "~m_string; 694 enforce(typeId!T == m_type, "Trying to access JSON"~dbg~" of type "~.to!string(m_type)~" as "~T.stringof~"."); 695 } 696 697 /*invariant() 698 { 699 assert(m_type >= Type.Undefined && m_type <= Type.Object); 700 }*/ 701 } 702 703 704 /******************************************************************************/ 705 /* public functions */ 706 /******************************************************************************/ 707 708 /** 709 Parses the given range as a JSON string and returns the corresponding Json object. 710 711 The range is shrunk during parsing, leaving any remaining text that is not part of 712 the JSON contents. 713 714 Throws an Exception if any parsing error occured. 715 */ 716 Json parseJson(R)(ref R range, int* line = null) 717 if( is(R == string) ) 718 { 719 Json ret; 720 enforce(!range.empty, "JSON string is empty."); 721 722 skipWhitespace(range, line); 723 724 version(JsonLineNumbers){ 725 import vibecompat.core.log; 726 int curline = line ? *line : 0; 727 scope(failure) logError("Error in line: %d", curline); 728 } 729 730 switch( range.front ){ 731 case 'f': 732 enforce(range[1 .. $].startsWith("alse"), "Expected 'false', got '"~range[0 .. 5]~"'."); 733 range.popFrontN(5); 734 ret = false; 735 break; 736 case 'n': 737 enforce(range[1 .. $].startsWith("ull"), "Expected 'null', got '"~range[0 .. 4]~"'."); 738 range.popFrontN(4); 739 ret = null; 740 break; 741 case 't': 742 enforce(range[1 .. $].startsWith("rue"), "Expected 'true', got '"~range[0 .. 4]~"'."); 743 range.popFrontN(4); 744 ret = true; 745 break; 746 case '0': .. case '9'+1: 747 case '-': 748 bool is_float; 749 auto num = skipNumber(range, is_float); 750 if( is_float ) ret = to!double(num); 751 else ret = to!long(num); 752 break; 753 case '\"': 754 ret = skipJsonString(range); 755 break; 756 case '[': 757 Json[] arr; 758 range.popFront(); 759 while(true) { 760 skipWhitespace(range, line); 761 enforce(!range.empty); 762 if(range.front == ']') break; 763 arr ~= parseJson(range, line); 764 skipWhitespace(range, line); 765 enforce(!range.empty && (range.front == ',' || range.front == ']'), "Expected ']' or ','."); 766 if( range.front == ']' ) break; 767 else range.popFront(); 768 } 769 range.popFront(); 770 ret = arr; 771 break; 772 case '{': 773 Json[string] obj; 774 range.popFront(); 775 while(true) { 776 skipWhitespace(range, line); 777 enforce(!range.empty); 778 if(range.front == '}') break; 779 string key = skipJsonString(range); 780 skipWhitespace(range, line); 781 enforce(range.startsWith(":"), "Expected ':' for key '" ~ key ~ "'"); 782 range.popFront(); 783 skipWhitespace(range, line); 784 Json itm = parseJson(range, line); 785 obj[key] = itm; 786 skipWhitespace(range, line); 787 enforce(!range.empty && (range.front == ',' || range.front == '}'), "Expected '}' or ',' - got '"~range[0]~"'."); 788 if( range.front == '}' ) break; 789 else range.popFront(); 790 } 791 range.popFront(); 792 ret = obj; 793 break; 794 default: 795 enforce(false, "Expected valid json token, got '"~to!string(range.length)~range[0 .. range.length>12?12:range.length]~"'."); 796 } 797 798 assert(ret.type != Json.Type.Undefined); 799 version(JsonLineNumbers) ret.line = curline; 800 return ret; 801 } 802 803 /** 804 Parses the given JSON string and returns the corresponding Json object. 805 806 Throws an Exception if any parsing error occurs. 807 */ 808 Json parseJsonString(string str) 809 { 810 auto ret = parseJson(str); 811 enforce(str.strip().length == 0, "Expected end of string after JSON value."); 812 return ret; 813 } 814 815 unittest { 816 assert(parseJsonString("null") == Json(null)); 817 assert(parseJsonString("true") == Json(true)); 818 assert(parseJsonString("false") == Json(false)); 819 assert(parseJsonString("1") == Json(1)); 820 assert(parseJsonString("2.0") == Json(2.0)); 821 assert(parseJsonString("\"test\"") == Json("test")); 822 assert(parseJsonString("[1, 2, 3]") == Json([Json(1), Json(2), Json(3)])); 823 assert(parseJsonString("{\"a\": 1}") == Json(["a": Json(1)])); 824 assert(parseJsonString(`"\\\/\b\f\n\r\t\u1234"`).get!string == "\\/\b\f\n\r\t\u1234"); 825 } 826 827 828 /** 829 Serializes the given value to JSON. 830 831 The following types of values are supported: 832 833 $(DL 834 $(DT Json) $(DD Used as-is) 835 $(DT null) $(DD Converted to Json.Type.Null) 836 $(DT bool) $(DD Converted to Json.Type.Bool) 837 $(DT float, double) $(DD Converted to Json.Type.Double) 838 $(DT short, ushort, int, uint, long, ulong) $(DD Converted to Json.Type.Int) 839 $(DT string) $(DD Converted to Json.Type.String) 840 $(DT T[]) $(DD Converted to Json.Type.Array) 841 $(DT T[string]) $(DD Converted to Json.Type.Object) 842 $(DT struct) $(DD Converted to Json.Type.Object) 843 $(DT class) $(DD Converted to Json.Type.Object or Json.Type.Null) 844 ) 845 846 All entries of an array or an associative array, as well as all R/W properties and 847 all public fields of a struct/class are recursively serialized using the same rules. 848 849 Fields ending with an underscore will have the last underscore stripped in the 850 serialized output. This makes it possible to use fields with D keywords as their name 851 by simply appending an underscore. 852 853 The following methods can be used to customize the serialization of structs/classes: 854 855 --- 856 Json toJson() const; 857 static T fromJson(Json src); 858 859 string toString() const; 860 static T fromString(string src); 861 --- 862 863 The methods will have to be defined in pairs. The first pair that is implemented by 864 the type will be used for serialization (i.e. toJson overrides toString). 865 */ 866 Json serializeToJson(T)(T value) 867 { 868 alias Unqual!T TU; 869 static if( is(TU == Json) ) return value; 870 else static if( is(TU == typeof(null)) ) return Json(null); 871 else static if( is(TU == bool) ) return Json(value); 872 else static if( is(TU == float) ) return Json(cast(double)value); 873 else static if( is(TU == double) ) return Json(value); 874 else static if( is(TU == DateTime) ) return Json(value.toISOExtString()); 875 else static if( is(TU == SysTime) ) return Json(value.toISOExtString()); 876 else static if( is(TU : long) ) return Json(cast(long)value); 877 else static if( is(TU == string) ) return Json(value); 878 else static if( isArray!T ){ 879 auto ret = new Json[value.length]; 880 foreach( i; 0 .. value.length ) 881 ret[i] = serializeToJson(value[i]); 882 return Json(ret); 883 } else static if( isAssociativeArray!TU ){ 884 Json[string] ret; 885 foreach( string key, value; value ) 886 ret[key] = serializeToJson(value); 887 return Json(ret); 888 } else static if( __traits(compiles, value = T.fromJson(value.toJson())) ){ 889 return value.toJson(); 890 } else static if( __traits(compiles, value = T.fromString(value.toString())) ){ 891 return Json(value.toString()); 892 } else static if( is(TU == struct) ){ 893 Json[string] ret; 894 foreach( m; __traits(allMembers, T) ){ 895 static if( isRWField!(TU, m) ){ 896 auto mv = __traits(getMember, value, m); 897 ret[underscoreStrip(m)] = serializeToJson(mv); 898 } 899 } 900 return Json(ret); 901 } else static if( is(TU == class) ){ 902 if( value is null ) return Json(null); 903 Json[string] ret; 904 foreach( m; __traits(allMembers, T) ){ 905 static if( isRWField!(TU, m) ){ 906 auto mv = __traits(getMember, value, m); 907 ret[underscoreStrip(m)] = serializeToJson(mv); 908 } 909 } 910 return Json(ret); 911 } else static if( isPointer!TU ){ 912 if( value is null ) return Json(null); 913 return serializeToJson(*value); 914 } else { 915 static assert(false, "Unsupported type '"~T.stringof~"' for JSON serialization."); 916 } 917 } 918 919 920 /** 921 Deserializes a JSON value into the destination variable. 922 923 The same types as for serializeToJson() are supported and handled inversely. 924 */ 925 void deserializeJson(T)(ref T dst, Json src) 926 { 927 dst = deserializeJson!T(src); 928 } 929 /// ditto 930 T deserializeJson(T)(Json src) 931 { 932 static if( is(T == Json) ) return src; 933 else static if( is(T == typeof(null)) ){ return null; } 934 else static if( is(T == bool) ) return src.get!bool; 935 else static if( is(T == float) ) return src.to!float; // since doubles are frequently serialized without 936 else static if( is(T == double) ) return src.to!double; // a decimal point, we allow conversions here 937 else static if( is(T == DateTime) ) return DateTime.fromISOExtString(src.get!string); 938 else static if( is(T == SysTime) ) return SysTime.fromISOExtString(src.get!string); 939 else static if( is(T : long) ) return cast(T)src.get!long; 940 else static if( is(T == string) ) return src.get!string; 941 else static if( isArray!T ){ 942 alias typeof(T.init[0]) TV; 943 auto dst = new Unqual!TV[src.length]; 944 foreach( size_t i, v; src ) 945 dst[i] = deserializeJson!(Unqual!TV)(v); 946 return dst; 947 } else static if( isAssociativeArray!T ){ 948 alias typeof(T.init.values[0]) TV; 949 Unqual!TV[string] dst; 950 foreach( string key, value; src ) 951 dst[key] = deserializeJson!(Unqual!TV)(value); 952 return dst; 953 } else static if( __traits(compiles, { T dst; dst = T.fromJson(dst.toJson()); }()) ){ 954 return T.fromJson(src); 955 } else static if( __traits(compiles, { T dst; dst = T.fromString(dst.toString()); }()) ){ 956 return T.fromString(src.get!string); 957 } else static if( is(T == struct) ){ 958 T dst; 959 foreach( m; __traits(allMembers, T) ){ 960 static if( isRWPlainField!(T, m) || isRWField!(T, m) ){ 961 alias typeof(__traits(getMember, dst, m)) TM; 962 __traits(getMember, dst, m) = deserializeJson!TM(src[underscoreStrip(m)]); 963 } 964 } 965 return dst; 966 } else static if( is(T == class) ){ 967 if( src.type == Json.Type.Null ) return null; 968 auto dst = new T; 969 foreach( m; __traits(allMembers, T) ){ 970 static if( isRWPlainField!(T, m) || isRWField!(T, m) ){ 971 alias typeof(__traits(getMember, dst, m)) TM; 972 __traits(getMember, dst, m) = deserializeJson!TM(src[underscoreStrip(m)]); 973 } 974 } 975 return dst; 976 } else static if( isPointer!T ){ 977 if( src.type == Json.Type.Null ) return null; 978 alias typeof(*T.init) TD; 979 dst = new TD; 980 *dst = deserializeJson!TD(src); 981 return dst; 982 } else { 983 static assert(false, "Unsupported type '"~T.stringof~"' for JSON serialization."); 984 } 985 } 986 987 unittest { 988 import std.stdio; 989 static struct S { float a; double b; bool c; int d; string e; byte f; ubyte g; long h; ulong i; float[] j; } 990 immutable S t = {1.5, -3.0, true, int.min, "Test", -128, 255, long.min, ulong.max, [1.1, 1.2, 1.3]}; 991 S u; 992 deserializeJson(u, serializeToJson(t)); 993 assert(t.a == u.a); 994 assert(t.b == u.b); 995 assert(t.c == u.c); 996 assert(t.d == u.d); 997 assert(t.e == u.e); 998 assert(t.f == u.f); 999 assert(t.g == u.g); 1000 assert(t.h == u.h); 1001 assert(t.i == u.i); 1002 assert(t.j == u.j); 1003 } 1004 1005 unittest { 1006 static class C { 1007 int a; 1008 private int _b; 1009 @property int b() const { return _b; } 1010 @property void b(int v) { _b = v; } 1011 1012 @property int test() const { return 10; } 1013 1014 void test2() {} 1015 } 1016 C c = new C; 1017 c.a = 1; 1018 c.b = 2; 1019 1020 C d; 1021 deserializeJson(d, serializeToJson(c)); 1022 assert(c.a == d.a); 1023 assert(c.b == d.b); 1024 } 1025 1026 1027 /** 1028 Writes the given JSON object as a JSON string into the destination range. 1029 1030 This function will convert the given JSON value to a string without adding 1031 any white space between tokens (no newlines, no indentation and no padding). 1032 The output size is thus minizized, at the cost of bad human readability. 1033 1034 Params: 1035 dst = References the string output range to which the result is written. 1036 json = Specifies the JSON value that is to be stringified. 1037 1038 See_Also: Json.toString, writePrettyJsonString 1039 */ 1040 void writeJsonString(R)(ref R dst, in Json json) 1041 // if( isOutputRange!R && is(ElementEncodingType!R == char) ) 1042 { 1043 final switch( json.type ){ 1044 case Json.Type.Undefined: dst.put("undefined"); break; 1045 case Json.Type.Null: dst.put("null"); break; 1046 case Json.Type.Bool: dst.put(cast(bool)json ? "true" : "false"); break; 1047 case Json.Type.Int: formattedWrite(dst, "%d", json.get!long); break; 1048 case Json.Type.Float: formattedWrite(dst, "%.16g", json.get!double); break; 1049 case Json.Type.String: 1050 dst.put("\""); 1051 jsonEscape(dst, cast(string)json); 1052 dst.put("\""); 1053 break; 1054 case Json.Type.Array: 1055 dst.put("["); 1056 bool first = true; 1057 foreach( ref const Json e; json ){ 1058 if( e.type == Json.Type.Undefined ) continue; 1059 if( !first ) dst.put(","); 1060 first = false; 1061 writeJsonString(dst, e); 1062 } 1063 dst.put("]"); 1064 break; 1065 case Json.Type.Object: 1066 dst.put("{"); 1067 bool first = true; 1068 foreach( string k, ref const Json e; json ){ 1069 if( e.type == Json.Type.Undefined ) continue; 1070 if( !first ) dst.put(","); 1071 first = false; 1072 dst.put("\""); 1073 jsonEscape(dst, k); 1074 dst.put("\":"); 1075 writeJsonString(dst, e); 1076 } 1077 dst.put("}"); 1078 break; 1079 } 1080 } 1081 1082 /** 1083 Writes the given JSON object as a prettified JSON string into the destination range. 1084 1085 The output will contain newlines and indents to make the output human readable. 1086 1087 Params: 1088 dst = References the string output range to which the result is written. 1089 json = Specifies the JSON value that is to be stringified. 1090 level = Specifies the base amount of indentation for the output. Indentation is always 1091 done using tab characters. 1092 1093 See_Also: Json.toPrettyString, writeJsonString 1094 */ 1095 void writePrettyJsonString(R)(ref R dst, in Json json, int level = 0) 1096 // if( isOutputRange!R && is(ElementEncodingType!R == char) ) 1097 { 1098 final switch( json.type ){ 1099 case Json.Type.Undefined: dst.put("undefined"); break; 1100 case Json.Type.Null: dst.put("null"); break; 1101 case Json.Type.Bool: dst.put(cast(bool)json ? "true" : "false"); break; 1102 case Json.Type.Int: formattedWrite(dst, "%d", json.get!long); break; 1103 case Json.Type.Float: formattedWrite(dst, "%.16g", json.get!double); break; 1104 case Json.Type.String: 1105 dst.put("\""); 1106 jsonEscape(dst, cast(string)json); 1107 dst.put("\""); 1108 break; 1109 case Json.Type.Array: 1110 dst.put("["); 1111 bool first = true; 1112 foreach( e; json ){ 1113 if( e.type == Json.Type.Undefined ) continue; 1114 if( !first ) dst.put(","); 1115 first = false; 1116 dst.put("\n"); 1117 foreach( tab; 0 .. level+1 ) dst.put('\t'); 1118 writePrettyJsonString(dst, e, level+1); 1119 } 1120 if( json.length > 0 ) { 1121 dst.put('\n'); 1122 foreach( tab; 0 .. level ) dst.put('\t'); 1123 } 1124 dst.put("]"); 1125 break; 1126 case Json.Type.Object: 1127 dst.put("{"); 1128 bool first = true; 1129 foreach( string k, e; json ){ 1130 if( e.type == Json.Type.Undefined ) continue; 1131 if( !first ) dst.put(","); 1132 dst.put("\n"); 1133 first = false; 1134 foreach( tab; 0 .. level+1 ) dst.put('\t'); 1135 dst.put("\""); 1136 jsonEscape(dst, k); 1137 dst.put("\": "); 1138 writePrettyJsonString(dst, e, level+1); 1139 } 1140 if( json.length > 0 ) { 1141 dst.put('\n'); 1142 foreach( tab; 0 .. level ) dst.put('\t'); 1143 } 1144 dst.put("}"); 1145 break; 1146 } 1147 } 1148 1149 /// private 1150 private void jsonEscape(R)(ref R dst, string s) 1151 { 1152 foreach( ch; s ){ 1153 switch(ch){ 1154 default: dst.put(ch); break; 1155 case '\\': dst.put("\\\\"); break; 1156 case '\r': dst.put("\\r"); break; 1157 case '\n': dst.put("\\n"); break; 1158 case '\t': dst.put("\\t"); break; 1159 case '\"': dst.put("\\\""); break; 1160 } 1161 } 1162 } 1163 1164 /// private 1165 private string jsonUnescape(R)(ref R range) 1166 { 1167 auto ret = appender!string(); 1168 while(!range.empty){ 1169 auto ch = range.front; 1170 switch( ch ){ 1171 case '"': return ret.data; 1172 case '\\': 1173 range.popFront(); 1174 enforce(!range.empty, "Unterminated string escape sequence."); 1175 switch(range.front){ 1176 default: enforce("Invalid string escape sequence."); break; 1177 case '"': ret.put('\"'); range.popFront(); break; 1178 case '\\': ret.put('\\'); range.popFront(); break; 1179 case '/': ret.put('/'); range.popFront(); break; 1180 case 'b': ret.put('\b'); range.popFront(); break; 1181 case 'f': ret.put('\f'); range.popFront(); break; 1182 case 'n': ret.put('\n'); range.popFront(); break; 1183 case 'r': ret.put('\r'); range.popFront(); break; 1184 case 't': ret.put('\t'); range.popFront(); break; 1185 case 'u': 1186 range.popFront(); 1187 dchar uch = 0; 1188 foreach( i; 0 .. 4 ){ 1189 uch *= 16; 1190 enforce(!range.empty, "Unicode sequence must be '\\uXXXX'."); 1191 auto dc = range.front; 1192 range.popFront(); 1193 if( dc >= '0' && dc <= '9' ) uch += dc - '0'; 1194 else if( dc >= 'a' && dc <= 'f' ) uch += dc - 'a' + 10; 1195 else if( dc >= 'A' && dc <= 'F' ) uch += dc - 'A' + 10; 1196 else enforce(false, "Unicode sequence must be '\\uXXXX'."); 1197 } 1198 ret.put(uch); 1199 break; 1200 } 1201 break; 1202 default: 1203 ret.put(ch); 1204 range.popFront(); 1205 break; 1206 } 1207 } 1208 return ret.data; 1209 } 1210 1211 private string skipNumber(ref string s, out bool is_float) 1212 { 1213 size_t idx = 0; 1214 is_float = false; 1215 if( s[idx] == '-' ) idx++; 1216 if( s[idx] == '0' ) idx++; 1217 else { 1218 enforce(isDigit(s[idx++]), "Digit expected at beginning of number."); 1219 while( idx < s.length && isDigit(s[idx]) ) idx++; 1220 } 1221 1222 if( idx < s.length && s[idx] == '.' ){ 1223 idx++; 1224 is_float = true; 1225 while( idx < s.length && isDigit(s[idx]) ) idx++; 1226 } 1227 1228 if( idx < s.length && (s[idx] == 'e' || s[idx] == 'E') ){ 1229 idx++; 1230 is_float = true; 1231 if( idx < s.length && (s[idx] == '+' || s[idx] == '-') ) idx++; 1232 enforce( idx < s.length && isDigit(s[idx]), "Expected exponent." ~ s[0 .. idx]); 1233 idx++; 1234 while( idx < s.length && isDigit(s[idx]) ) idx++; 1235 } 1236 1237 string ret = s[0 .. idx]; 1238 s = s[idx .. $]; 1239 return ret; 1240 } 1241 1242 private string skipJsonString(ref string s, int* line = null) 1243 { 1244 enforce(s.length >= 2 && s[0] == '\"', "too small: '" ~ s ~ "'"); 1245 s = s[1 .. $]; 1246 string ret = jsonUnescape(s); 1247 enforce(s.length > 0 && s[0] == '\"', "Unterminated string literal."); 1248 s = s[1 .. $]; 1249 return ret; 1250 } 1251 1252 private void skipWhitespace(ref string s, int* line = null) 1253 { 1254 while( s.length > 0 ){ 1255 switch( s[0] ){ 1256 default: return; 1257 case ' ', '\t': s = s[1 .. $]; break; 1258 case '\n': 1259 s = s[1 .. $]; 1260 if( s.length > 0 && s[0] == '\r' ) s = s[1 .. $]; 1261 if( line ) (*line)++; 1262 break; 1263 case '\r': 1264 s = s[1 .. $]; 1265 if( s.length > 0 && s[0] == '\n' ) s = s[1 .. $]; 1266 if( line ) (*line)++; 1267 break; 1268 } 1269 } 1270 } 1271 1272 /// private 1273 private bool isDigit(T)(T ch){ return ch >= '0' && ch <= '9'; } 1274 1275 private string underscoreStrip(string field_name) 1276 { 1277 if( field_name.length < 1 || field_name[$-1] != '_' ) return field_name; 1278 else return field_name[0 .. $-1]; 1279 }