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