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