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 conversions 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-2013 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 vson.json; 41 42 public import vson.serialization; 43 44 public import std.json : JSONException; 45 46 import std.algorithm : equal, min; 47 import std.array; 48 import std.conv; 49 import std.datetime; 50 import std.exception; 51 import std.format; 52 import std.string; 53 import std.range; 54 import std.traits; 55 56 57 /******************************************************************************/ 58 /* public types */ 59 /******************************************************************************/ 60 61 /** 62 Represents a single JSON value. 63 64 Json values can have one of the types defined in the Json.Type enum. They 65 behave mostly like values in ECMA script in the way that you can 66 transparently perform operations on them. However, strict typechecking is 67 done, so that operations between differently typed JSON values will throw 68 a JSONException. Additionally, an explicit cast or using get!() or to!() is 69 required to convert a JSON value to the corresponding static D type. 70 */ 71 struct Json { 72 private { 73 // putting all fields in a union results in many false pointers leading to 74 // memory leaks and, worse, std.algorithm.swap triggering an assertion 75 // because of internal pointers. This crude workaround seems to fix 76 // the issues. 77 void*[2] m_data; 78 ref inout(T) getDataAs(T)() inout { static assert(T.sizeof <= m_data.sizeof); return *cast(inout(T)*)m_data.ptr; } 79 @property ref inout(long) m_int() inout { return getDataAs!long(); } 80 @property ref inout(double) m_float() inout { return getDataAs!double(); } 81 @property ref inout(bool) m_bool() inout { return getDataAs!bool(); } 82 @property ref inout(string) m_string() inout { return getDataAs!string(); } 83 @property ref inout(Json[string]) m_object() inout { return getDataAs!(Json[string])(); } 84 @property ref inout(Json[]) m_array() inout { return getDataAs!(Json[])(); } 85 86 Type m_type = Type.undefined; 87 88 version (VibeJsonFieldNames) { 89 uint m_magic = 0x1337f00d; // works around Appender bug (DMD BUG 10690/10859/11357) 90 string m_name; 91 } 92 } 93 94 /** Represents the run time type of a JSON value. 95 */ 96 enum Type { 97 undefined, /// A non-existent value in a JSON object 98 null_, /// Null value 99 bool_, /// Boolean value 100 int_, /// 64-bit integer value 101 float_, /// 64-bit floating point value 102 string, /// UTF-8 string 103 array, /// Array of JSON values 104 object, /// JSON object aka. dictionary from string to Json 105 106 Undefined = undefined, /// Compatibility alias - will be deprecated soon 107 Null = null_, /// Compatibility alias - will be deprecated soon 108 Bool = bool_, /// Compatibility alias - will be deprecated soon 109 Int = int_, /// Compatibility alias - will be deprecated soon 110 Float = float_, /// Compatibility alias - will be deprecated soon 111 String = string, /// Compatibility alias - will be deprecated soon 112 Array = array, /// Compatibility alias - will be deprecated soon 113 Object = object /// Compatibility alias - will be deprecated soon 114 } 115 116 /// New JSON value of Type.Undefined 117 static @property Json undefined() { return Json(); } 118 119 /// New JSON value of Type.Object 120 static @property Json emptyObject() { return Json(cast(Json[string])null); } 121 122 /// New JSON value of Type.Array 123 static @property Json emptyArray() { return Json(cast(Json[])null); } 124 125 version(JsonLineNumbers) int line; 126 127 /** 128 Constructor for a JSON object. 129 */ 130 this(typeof(null)) { m_type = Type.null_; } 131 /// ditto 132 this(bool v) { m_type = Type.bool_; m_bool = v; } 133 /// ditto 134 this(byte v) { this(cast(long)v); } 135 /// ditto 136 this(ubyte v) { this(cast(long)v); } 137 /// ditto 138 this(short v) { this(cast(long)v); } 139 /// ditto 140 this(ushort v) { this(cast(long)v); } 141 /// ditto 142 this(int v) { this(cast(long)v); } 143 /// ditto 144 this(uint v) { this(cast(long)v); } 145 /// ditto 146 this(long v) { m_type = Type.int_; m_int = v; } 147 /// ditto 148 this(double v) { m_type = Type.float_; m_float = v; } 149 /// ditto 150 this(string v) { m_type = Type..string; m_string = v; } 151 /// ditto 152 this(Json[] v) { m_type = Type.array; m_array = v; } 153 /// ditto 154 this(Json[string] v) { m_type = Type.object; m_object = v; } 155 156 /** 157 Allows assignment of D values to a JSON value. 158 */ 159 ref Json opAssign(Json v) 160 { 161 m_type = v.m_type; 162 final switch(m_type){ 163 case Type.undefined: m_string = null; break; 164 case Type.null_: m_string = null; break; 165 case Type.bool_: m_bool = v.m_bool; break; 166 case Type.int_: m_int = v.m_int; break; 167 case Type.float_: m_float = v.m_float; break; 168 case Type..string: m_string = v.m_string; break; 169 case Type.array: opAssign(v.m_array); break; 170 case Type.object: opAssign(v.m_object); break; 171 } 172 return this; 173 } 174 /// ditto 175 void opAssign(typeof(null)) { m_type = Type.null_; m_string = null; } 176 /// ditto 177 bool opAssign(bool v) { m_type = Type.bool_; m_bool = v; return v; } 178 /// ditto 179 int opAssign(int v) { m_type = Type.int_; m_int = v; return v; } 180 /// ditto 181 long opAssign(long v) { m_type = Type.int_; m_int = v; return v; } 182 /// ditto 183 double opAssign(double v) { m_type = Type.float_; m_float = v; return v; } 184 /// ditto 185 string opAssign(string v) { m_type = Type..string; m_string = v; return v; } 186 /// ditto 187 Json[] opAssign(Json[] v) 188 { 189 m_type = Type.array; 190 m_array = v; 191 version (VibeJsonFieldNames) { if (m_magic == 0x1337f00d) { foreach (idx, ref av; m_array) av.m_name = format("%s[%s]", m_name, idx); } else m_name = null; } 192 return v; 193 } 194 /// ditto 195 Json[string] opAssign(Json[string] v) 196 { 197 m_type = Type.object; 198 m_object = v; 199 version (VibeJsonFieldNames) { if (m_magic == 0x1337f00d) { foreach (key, ref av; m_object) av.m_name = format("%s.%s", m_name, key); } else m_name = null; } 200 return v; 201 } 202 203 /** 204 Allows removal of values from Type.Object Json objects. 205 */ 206 void remove(string item) { checkType!(Json[string])(); m_object.remove(item); } 207 208 /** 209 The current type id of this JSON object. 210 */ 211 @property Type type() const { return m_type; } 212 213 /** 214 Allows direct indexing of array typed JSON values. 215 */ 216 ref inout(Json) opIndex(size_t idx) inout { checkType!(Json[])(); return m_array[idx]; } 217 218 Json clone() const { 219 final switch (m_type) { 220 case Type.undefined: return Json.undefined; 221 case Type.null_: return Json(null); 222 case Type.bool_: return Json(m_bool); 223 case Type.int_: return Json(m_int); 224 case Type.float_: return Json(m_float); 225 case Type..string: return Json(m_string); 226 case Type.array: 227 auto ret = Json.emptyArray; 228 foreach (v; this) ret ~= v.clone(); 229 return ret; 230 case Type.object: 231 auto ret = Json.emptyObject; 232 foreach (string name, v; this) ret[name] = v.clone(); 233 return ret; 234 } 235 } 236 237 /** 238 Allows direct indexing of object typed JSON values using a string as 239 the key. 240 */ 241 const(Json) opIndex(string key) 242 const { 243 checkType!(Json[string])(); 244 if( auto pv = key in m_object ) return *pv; 245 Json ret = Json.undefined; 246 ret.m_string = key; 247 version (VibeJsonFieldNames) ret.m_name = format("%s.%s", m_name, key); 248 return ret; 249 } 250 /// ditto 251 ref Json opIndex(string key) 252 { 253 checkType!(Json[string])(); 254 if( auto pv = key in m_object ) 255 return *pv; 256 if (m_object is null) { 257 m_object = ["": Json.init]; 258 m_object.remove(""); 259 } 260 m_object[key] = Json.init; 261 assert(m_object !is null); 262 assert(key in m_object, "Failed to insert key '"~key~"' into AA!?"); 263 m_object[key].m_type = Type.undefined; // DMDBUG: AAs are teh $H1T!!!11 264 assert(m_object[key].type == Type.undefined); 265 m_object[key].m_string = key; 266 version (VibeJsonFieldNames) m_object[key].m_name = format("%s.%s", m_name, key); 267 return m_object[key]; 268 } 269 270 /** 271 Returns a slice of a JSON array. 272 */ 273 inout(Json[]) opSlice() inout { checkType!(Json[])(); return m_array; } 274 /// 275 inout(Json[]) opSlice(size_t from, size_t to) inout { checkType!(Json[])(); return m_array[from .. to]; } 276 277 /** 278 Returns the number of entries of string, array or object typed JSON values. 279 */ 280 @property size_t length() 281 const { 282 checkType!(string, Json[], Json[string])("property length"); 283 switch(m_type){ 284 case Type..string: return m_string.length; 285 case Type.array: return m_array.length; 286 case Type.object: return m_object.length; 287 default: assert(false); 288 } 289 } 290 291 /** 292 Allows foreach iterating over JSON objects and arrays. 293 */ 294 int opApply(int delegate(ref Json obj) del) 295 { 296 checkType!(Json[], Json[string])("opApply"); 297 if( m_type == Type.array ){ 298 foreach( ref v; m_array ) 299 if( auto ret = del(v) ) 300 return ret; 301 return 0; 302 } else { 303 foreach( ref v; m_object ) 304 if( v.type != Type.undefined ) 305 if( auto ret = del(v) ) 306 return ret; 307 return 0; 308 } 309 } 310 /// ditto 311 int opApply(int delegate(ref const Json obj) del) 312 const { 313 checkType!(Json[], Json[string])("opApply"); 314 if( m_type == Type.array ){ 315 foreach( ref v; m_array ) 316 if( auto ret = del(v) ) 317 return ret; 318 return 0; 319 } else { 320 foreach( ref v; m_object ) 321 if( v.type != Type.undefined ) 322 if( auto ret = del(v) ) 323 return ret; 324 return 0; 325 } 326 } 327 /// ditto 328 int opApply(int delegate(ref size_t idx, ref Json obj) del) 329 { 330 checkType!(Json[])("opApply"); 331 foreach( idx, ref v; m_array ) 332 if( auto ret = del(idx, v) ) 333 return ret; 334 return 0; 335 } 336 /// ditto 337 int opApply(int delegate(ref size_t idx, ref const Json obj) del) 338 const { 339 checkType!(Json[])("opApply"); 340 foreach( idx, ref v; m_array ) 341 if( auto ret = del(idx, v) ) 342 return ret; 343 return 0; 344 } 345 /// ditto 346 int opApply(int delegate(ref string idx, ref Json obj) del) 347 { 348 checkType!(Json[string])("opApply"); 349 foreach( idx, ref v; m_object ) 350 if( v.type != Type.undefined ) 351 if( auto ret = del(idx, v) ) 352 return ret; 353 return 0; 354 } 355 /// ditto 356 int opApply(int delegate(ref string idx, ref const Json obj) del) 357 const { 358 checkType!(Json[string])("opApply"); 359 foreach( idx, ref v; m_object ) 360 if( v.type != Type.undefined ) 361 if( auto ret = del(idx, v) ) 362 return ret; 363 return 0; 364 } 365 366 /** 367 Converts the JSON value to the corresponding D type - types must match exactly. 368 */ 369 inout(T) opCast(T)() inout { return get!T; } 370 /// ditto 371 @property inout(T) get(T)() 372 inout { 373 checkType!T(); 374 static if (is(T == bool)) return m_bool; 375 else static if (is(T == double)) return m_float; 376 else static if (is(T == float)) return cast(T)m_float; 377 else static if (is(T == long)) return m_int; 378 else static if (is(T == ulong)) return cast(ulong)m_int; 379 else static if (is(T : long)){ enforceJson(m_int <= T.max && m_int >= T.min); return cast(T)m_int; } 380 else static if (is(T == string)) return m_string; 381 else static if (is(T == Json[])) return m_array; 382 else static if (is(T == Json[string])) return m_object; 383 else static assert("JSON can only be cast to (bool, long, double, string, Json[] or Json[string]. Not "~T.stringof~"."); 384 } 385 386 /** 387 Returns the native type for this JSON if it matches the current runtime type. 388 389 If the runtime type does not match the given native type, the 'def' parameter is returned 390 instead. 391 */ 392 @property const(T) opt(T)(const(T) def = T.init) 393 const { 394 if( typeId!T != m_type ) return def; 395 return get!T; 396 } 397 /// ditto 398 @property T opt(T)(T def = T.init) 399 { 400 if( typeId!T != m_type ) return def; 401 return get!T; 402 } 403 404 /** 405 Converts the JSON value to the corresponding D type - types are converted as necessary. 406 */ 407 @property inout(T) to(T)() 408 inout { 409 static if( is(T == bool) ){ 410 final switch( m_type ){ 411 case Type.undefined: return false; 412 case Type.null_: return false; 413 case Type.bool_: return m_bool; 414 case Type.int_: return m_int != 0; 415 case Type.float_: return m_float != 0; 416 case Type..string: return m_string.length > 0; 417 case Type.array: return m_array.length > 0; 418 case Type.object: return m_object.length > 0; 419 } 420 } else static if( is(T == double) ){ 421 final switch( m_type ){ 422 case Type.undefined: return T.init; 423 case Type.null_: return 0; 424 case Type.bool_: return m_bool ? 1 : 0; 425 case Type.int_: return m_int; 426 case Type.float_: return m_float; 427 case Type..string: return .to!double(cast(string)m_string); 428 case Type.array: return double.init; 429 case Type.object: return double.init; 430 } 431 } else static if( is(T == float) ){ 432 final switch( m_type ){ 433 case Type.undefined: return T.init; 434 case Type.null_: return 0; 435 case Type.bool_: return m_bool ? 1 : 0; 436 case Type.int_: return m_int; 437 case Type.float_: return m_float; 438 case Type..string: return .to!float(cast(string)m_string); 439 case Type.array: return float.init; 440 case Type.object: return float.init; 441 } 442 } 443 else static if( is(T == long) ){ 444 final switch( m_type ){ 445 case Type.undefined: return 0; 446 case Type.null_: return 0; 447 case Type.bool_: return m_bool ? 1 : 0; 448 case Type.int_: return m_int; 449 case Type.float_: return cast(long)m_float; 450 case Type..string: return .to!long(m_string); 451 case Type.array: return 0; 452 case Type.object: return 0; 453 } 454 } else static if( is(T : long) ){ 455 final switch( m_type ){ 456 case Type.undefined: return 0; 457 case Type.null_: return 0; 458 case Type.bool_: return m_bool ? 1 : 0; 459 case Type.int_: return cast(T)m_int; 460 case Type.float_: return cast(T)m_float; 461 case Type..string: return cast(T).to!long(cast(string)m_string); 462 case Type.array: return 0; 463 case Type.object: return 0; 464 } 465 } else static if( is(T == string) ){ 466 switch( m_type ){ 467 default: return toString(); 468 case Type..string: return m_string; 469 } 470 } else static if( is(T == Json[]) ){ 471 switch( m_type ){ 472 default: return Json([this]); 473 case Type.array: return m_array; 474 } 475 } else static if( is(T == Json[string]) ){ 476 switch( m_type ){ 477 default: return Json(["value": this]); 478 case Type.object: return m_object; 479 } 480 } else static assert("JSON can only be cast to (bool, long, double, string, Json[] or Json[string]. Not "~T.stringof~"."); 481 } 482 483 /** 484 Performs unary operations on the JSON value. 485 486 The following operations are supported for each type: 487 488 $(DL 489 $(DT Null) $(DD none) 490 $(DT Bool) $(DD ~) 491 $(DT Int) $(DD +, -, ++, --) 492 $(DT Float) $(DD +, -, ++, --) 493 $(DT String) $(DD none) 494 $(DT Array) $(DD none) 495 $(DT Object) $(DD none) 496 ) 497 */ 498 Json opUnary(string op)() 499 const { 500 static if( op == "~" ){ 501 checkType!bool(); 502 return Json(~m_bool); 503 } else static if( op == "+" || op == "-" || op == "++" || op == "--" ){ 504 checkType!(long, double)("unary "~op); 505 if( m_type == Type.int_ ) mixin("return Json("~op~"m_int);"); 506 else if( m_type == Type.float_ ) mixin("return Json("~op~"m_float);"); 507 else assert(false); 508 } else static assert("Unsupported operator '"~op~"' for type JSON."); 509 } 510 511 /** 512 Performs binary operations between JSON values. 513 514 The two JSON values must be of the same run time type or a JSONException 515 will be thrown. Only the operations listed are allowed for each of the 516 types. 517 518 $(DL 519 $(DT Null) $(DD none) 520 $(DT Bool) $(DD &&, ||) 521 $(DT Int) $(DD +, -, *, /, %) 522 $(DT Float) $(DD +, -, *, /, %) 523 $(DT String) $(DD ~) 524 $(DT Array) $(DD ~) 525 $(DT Object) $(DD in) 526 ) 527 */ 528 Json opBinary(string op)(ref const(Json) other) 529 const { 530 enforceJson(m_type == other.m_type, "Binary operation '"~op~"' between "~.to!string(m_type)~" and "~.to!string(other.m_type)~" JSON objects."); 531 static if( op == "&&" ){ 532 checkType!(bool)(op); 533 return Json(m_bool && other.m_bool); 534 } else static if( op == "||" ){ 535 checkType!(bool)(op); 536 return Json(m_bool || other.m_bool); 537 } else static if( op == "+" ){ 538 checkType!(long, double)(op); 539 if( m_type == Type.Int ) return Json(m_int + other.m_int); 540 else if( m_type == Type.float_ ) return Json(m_float + other.m_float); 541 else assert(false); 542 } else static if( op == "-" ){ 543 checkType!(long, double)(op); 544 if( m_type == Type.Int ) return Json(m_int - other.m_int); 545 else if( m_type == Type.float_ ) return Json(m_float - other.m_float); 546 else assert(false); 547 } else static if( op == "*" ){ 548 checkType!(long, double)(op); 549 if( m_type == Type.Int ) return Json(m_int * other.m_int); 550 else if( m_type == Type.float_ ) return Json(m_float * other.m_float); 551 else assert(false); 552 } else static if( op == "/" ){ 553 checkType!(long, double)(op); 554 if( m_type == Type.Int ) return Json(m_int / other.m_int); 555 else if( m_type == Type.float_ ) return Json(m_float / other.m_float); 556 else assert(false); 557 } else static if( op == "%" ){ 558 checkType!(long, double)(op); 559 if( m_type == Type.Int ) return Json(m_int % other.m_int); 560 else if( m_type == Type.float_ ) return Json(m_float % other.m_float); 561 else assert(false); 562 } else static if( op == "~" ){ 563 checkType!(string, Json[])(op); 564 if( m_type == Type..string ) return Json(m_string ~ other.m_string); 565 else if (m_type == Type.array) return Json(m_array ~ other.m_array); 566 else assert(false); 567 } else static assert("Unsupported operator '"~op~"' for type JSON."); 568 } 569 /// ditto 570 Json opBinary(string op)(Json other) 571 if( op == "~" ) 572 { 573 static if( op == "~" ){ 574 checkType!(string, Json[])(op); 575 if( m_type == Type..string ) return Json(m_string ~ other.m_string); 576 else if( m_type == Type.array ) return Json(m_array ~ other.m_array); 577 else assert(false); 578 } else static assert("Unsupported operator '"~op~"' for type JSON."); 579 } 580 /// ditto 581 void opOpAssign(string op)(Json other) 582 if (op == "+" || op == "-" || op == "*" || op == "/" || op == "%" || op =="~") 583 { 584 enforceJson(m_type == other.m_type || op == "~" && m_type == Type.array, 585 "Binary operation '"~op~"=' between "~.to!string(m_type)~" and "~.to!string(other.m_type)~" JSON objects."); 586 static if( op == "+" ){ 587 if( m_type == Type.int_ ) m_int += other.m_int; 588 else if( m_type == Type.float_ ) m_float += other.m_float; 589 else enforceJson(false, "'+=' only allowed for scalar types, not "~.to!string(m_type)~"."); 590 } else static if( op == "-" ){ 591 if( m_type == Type.int_ ) m_int -= other.m_int; 592 else if( m_type == Type.float_ ) m_float -= other.m_float; 593 else enforceJson(false, "'-=' only allowed for scalar types, not "~.to!string(m_type)~"."); 594 } else static if( op == "*" ){ 595 if( m_type == Type.int_ ) m_int *= other.m_int; 596 else if( m_type == Type.float_ ) m_float *= other.m_float; 597 else enforceJson(false, "'*=' only allowed for scalar types, not "~.to!string(m_type)~"."); 598 } else static if( op == "/" ){ 599 if( m_type == Type.int_ ) m_int /= other.m_int; 600 else if( m_type == Type.float_ ) m_float /= other.m_float; 601 else enforceJson(false, "'/=' only allowed for scalar types, not "~.to!string(m_type)~"."); 602 } else static if( op == "%" ){ 603 if( m_type == Type.int_ ) m_int %= other.m_int; 604 else if( m_type == Type.float_ ) m_float %= other.m_float; 605 else enforceJson(false, "'%=' only allowed for scalar types, not "~.to!string(m_type)~"."); 606 } else static if( op == "~" ){ 607 if (m_type == Type..string) m_string ~= other.m_string; 608 else if (m_type == Type.array) { 609 if (other.m_type == Type.array) m_array ~= other.m_array; 610 else appendArrayElement(other); 611 } else enforceJson(false, "'~=' only allowed for string and array types, not "~.to!string(m_type)~"."); 612 } else static assert("Unsupported operator '"~op~"=' for type JSON."); 613 } 614 /// ditto 615 void opOpAssign(string op, T)(T other) 616 if (!is(T == Json) && is(typeof(Json(other)))) 617 { 618 opOpAssign!op(Json(other)); 619 } 620 /// ditto 621 Json opBinary(string op)(bool other) const { checkType!bool(); mixin("return Json(m_bool "~op~" other);"); } 622 /// ditto 623 Json opBinary(string op)(long other) const { checkType!long(); mixin("return Json(m_int "~op~" other);"); } 624 /// ditto 625 Json opBinary(string op)(double other) const { checkType!double(); mixin("return Json(m_float "~op~" other);"); } 626 /// ditto 627 Json opBinary(string op)(string other) const { checkType!string(); mixin("return Json(m_string "~op~" other);"); } 628 /// ditto 629 Json opBinary(string op)(Json[] other) { checkType!(Json[])(); mixin("return Json(m_array "~op~" other);"); } 630 /// ditto 631 Json opBinaryRight(string op)(bool other) const { checkType!bool(); mixin("return Json(other "~op~" m_bool);"); } 632 /// ditto 633 Json opBinaryRight(string op)(long other) const { checkType!long(); mixin("return Json(other "~op~" m_int);"); } 634 /// ditto 635 Json opBinaryRight(string op)(double other) const { checkType!double(); mixin("return Json(other "~op~" m_float);"); } 636 /// ditto 637 Json opBinaryRight(string op)(string other) const if(op == "~") { checkType!string(); return Json(other ~ m_string); } 638 /// ditto 639 inout(Json)* opBinaryRight(string op)(string other) inout if(op == "in") { 640 checkType!(Json[string])(); 641 auto pv = other in m_object; 642 if( !pv ) return null; 643 if( pv.type == Type.undefined ) return null; 644 return pv; 645 } 646 /// ditto 647 Json opBinaryRight(string op)(Json[] other) { checkType!(Json[])(); mixin("return Json(other "~op~" m_array);"); } 648 649 /** 650 * The append operator will append arrays. This method always appends it's argument as an array element, so nested arrays can be created. 651 */ 652 void appendArrayElement(Json element) 653 { 654 enforceJson(m_type == Type.array, "'appendArrayElement' only allowed for array types, not "~.to!string(m_type)~"."); 655 m_array ~= element; 656 } 657 658 /** 659 Allows to access existing fields of a JSON object using dot syntax. 660 */ 661 @property const(Json) opDispatch(string prop)() const { return opIndex(prop); } 662 /// ditto 663 @property ref Json opDispatch(string prop)() { return opIndex(prop); } 664 665 /** 666 Compares two JSON values for equality. 667 668 If the two values have different types, they are considered unequal. 669 This differs with ECMA script, which performs a type conversion before 670 comparing the values. 671 */ 672 bool opEquals(ref const Json other) 673 const { 674 if( m_type != other.m_type ) return false; 675 final switch(m_type){ 676 case Type.undefined: return false; 677 case Type.null_: return true; 678 case Type.bool_: return m_bool == other.m_bool; 679 case Type.int_: return m_int == other.m_int; 680 case Type.float_: return m_float == other.m_float; 681 case Type..string: return m_string == other.m_string; 682 case Type.array: return m_array == other.m_array; 683 case Type.object: return m_object == other.m_object; 684 } 685 } 686 /// ditto 687 bool opEquals(const Json other) const { return opEquals(other); } 688 /// ditto 689 bool opEquals(typeof(null)) const { return m_type == Type.null_; } 690 /// ditto 691 bool opEquals(bool v) const { return m_type == Type.bool_ && m_bool == v; } 692 /// ditto 693 bool opEquals(long v) const { return m_type == Type.int_ && m_int == v; } 694 /// ditto 695 bool opEquals(double v) const { return m_type == Type.float_ && m_float == v; } 696 /// ditto 697 bool opEquals(string v) const { return m_type == Type..string && m_string == v; } 698 699 /** 700 Compares two JSON values. 701 702 If the types of the two values differ, the value with the smaller type 703 id is considered the smaller value. This differs from ECMA script, which 704 performs a type conversion before comparing the values. 705 706 JSON values of type Object cannot be compared and will throw an 707 exception. 708 */ 709 int opCmp(ref const Json other) 710 const { 711 if( m_type != other.m_type ) return m_type < other.m_type ? -1 : 1; 712 final switch(m_type){ 713 case Type.undefined: return 0; 714 case Type.null_: return 0; 715 case Type.bool_: return m_bool < other.m_bool ? -1 : m_bool == other.m_bool ? 0 : 1; 716 case Type.int_: return m_int < other.m_int ? -1 : m_int == other.m_int ? 0 : 1; 717 case Type.float_: return m_float < other.m_float ? -1 : m_float == other.m_float ? 0 : 1; 718 case Type..string: return m_string < other.m_string ? -1 : m_string == other.m_string ? 0 : 1; 719 case Type.array: return m_array < other.m_array ? -1 : m_array == other.m_array ? 0 : 1; 720 case Type.object: 721 enforceJson(false, "JSON objects cannot be compared."); 722 assert(false); 723 } 724 } 725 726 alias opDollar = length; 727 728 /** 729 Returns the type id corresponding to the given D type. 730 */ 731 static @property Type typeId(T)() { 732 static if( is(T == typeof(null)) ) return Type.null_; 733 else static if( is(T == bool) ) return Type.bool_; 734 else static if( is(T == double) ) return Type.float_; 735 else static if( is(T == float) ) return Type.float_; 736 else static if( is(T : long) ) return Type.int_; 737 else static if( is(T == string) ) return Type..string; 738 else static if( is(T == Json[]) ) return Type.array; 739 else static if( is(T == Json[string]) ) return Type.object; 740 else static assert(false, "Unsupported JSON type '"~T.stringof~"'. Only bool, long, double, string, Json[] and Json[string] are allowed."); 741 } 742 743 /** 744 Returns the JSON object as a string. 745 746 For large JSON values use writeJsonString instead as this function will store the whole string 747 in memory, whereas writeJsonString writes it out bit for bit. 748 749 See_Also: writeJsonString, toPrettyString 750 */ 751 string toString() 752 const { 753 auto ret = appender!string(); 754 writeJsonString(ret, this); 755 return ret.data; 756 } 757 758 /** 759 Returns the JSON object as a "pretty" string. 760 761 --- 762 auto json = Json(["foo": Json("bar")]); 763 writeln(json.toPrettyString()); 764 765 // output: 766 // { 767 // "foo": "bar" 768 // } 769 --- 770 771 Params: 772 level = Specifies the base amount of indentation for the output. Indentation is always 773 done using tab characters. 774 775 See_Also: writePrettyJsonString, toString 776 */ 777 string toPrettyString(int level = 0) 778 const { 779 auto ret = appender!string(); 780 writePrettyJsonString(ret, this, level); 781 return ret.data; 782 } 783 784 private void checkType(TYPES...)(string op = null) 785 const { 786 bool matched = false; 787 foreach (T; TYPES) if (m_type == typeId!T) matched = true; 788 if (matched) return; 789 790 string name; 791 version (VibeJsonFieldNames) { 792 if (m_name.length) name = m_name ~ " of type " ~ m_type.to!string; 793 else name = "JSON of type " ~ m_type.to!string; 794 } else name = "JSON of type " ~ m_type.to!string; 795 796 string expected; 797 static if (TYPES.length == 1) expected = typeId!(TYPES[0]).to!string; 798 else { 799 foreach (T; TYPES) { 800 if (expected.length > 0) expected ~= ", "; 801 expected ~= typeId!T.to!string; 802 } 803 } 804 805 if (!op.length) throw new JSONException(format("Got %s, expected %s.", name, expected)); 806 else throw new JSONException(format("Got %s, expected %s for %s.", name, expected, op)); 807 } 808 809 /*invariant() 810 { 811 assert(m_type >= Type.Undefined && m_type <= Type.Object); 812 }*/ 813 } 814 815 816 /******************************************************************************/ 817 /* public functions */ 818 /******************************************************************************/ 819 820 /** 821 Parses the given range as a JSON string and returns the corresponding Json object. 822 823 The range is shrunk during parsing, leaving any remaining text that is not part of 824 the JSON contents. 825 826 Throws a JSONException if any parsing error occured. 827 */ 828 Json parseJson(R)(ref R range, int* line = null, string filename = null) 829 if( is(R == string) ) 830 { 831 Json ret; 832 enforceJson(!range.empty, "JSON string is empty.", filename, 0); 833 834 skipWhitespace(range, line); 835 836 version(JsonLineNumbers) { 837 import vibe.core.log; 838 int curline = line ? *line : 0; 839 } 840 841 switch( range.front ){ 842 case 'f': 843 enforceJson(range[1 .. $].startsWith("alse"), "Expected 'false', got '"~range[0 .. min(5, $)]~"'.", filename, line); 844 range.popFrontN(5); 845 ret = false; 846 break; 847 case 'n': 848 enforceJson(range[1 .. $].startsWith("ull"), "Expected 'null', got '"~range[0 .. min(4, $)]~"'.", filename, line); 849 range.popFrontN(4); 850 ret = null; 851 break; 852 case 't': 853 enforceJson(range[1 .. $].startsWith("rue"), "Expected 'true', got '"~range[0 .. min(4, $)]~"'.", filename, line); 854 range.popFrontN(4); 855 ret = true; 856 break; 857 case '0': .. case '9': 858 case '-': 859 bool is_float; 860 auto num = skipNumber(range, is_float); 861 if( is_float ) ret = to!double(num); 862 else ret = to!long(num); 863 break; 864 case '\"': 865 ret = skipJsonString(range); 866 break; 867 case '[': 868 Json[] arr; 869 range.popFront(); 870 while (true) { 871 skipWhitespace(range, line); 872 enforceJson(!range.empty, "Missing ']' before EOF.", filename, line); 873 if(range.front == ']') break; 874 arr ~= parseJson(range, line, filename); 875 skipWhitespace(range, line); 876 enforceJson(!range.empty, "Missing ']' before EOF.", filename, line); 877 enforceJson(range.front == ',' || range.front == ']', 878 format("Expected ']' or ',' - got '%s'.", range.front), filename, line); 879 if( range.front == ']' ) break; 880 else range.popFront(); 881 } 882 range.popFront(); 883 ret = arr; 884 break; 885 case '{': 886 Json[string] obj; 887 range.popFront(); 888 while (true) { 889 skipWhitespace(range, line); 890 enforceJson(!range.empty, "Missing '}' before EOF.", filename, line); 891 if(range.front == '}') break; 892 string key = skipJsonString(range); 893 skipWhitespace(range, line); 894 enforceJson(range.startsWith(":"), "Expected ':' for key '" ~ key ~ "'", filename, line); 895 range.popFront(); 896 skipWhitespace(range, line); 897 Json itm = parseJson(range, line, filename); 898 obj[key] = itm; 899 skipWhitespace(range, line); 900 enforceJson(!range.empty, "Missing '}' before EOF.", filename, line); 901 enforceJson(range.front == ',' || range.front == '}', 902 format("Expected '}' or ',' - got '%s'.", range.front), filename, line); 903 if (range.front == '}') break; 904 else range.popFront(); 905 } 906 range.popFront(); 907 ret = obj; 908 break; 909 default: 910 enforceJson(false, format("Expected valid JSON token, got '%s'.", range[0 .. min(12, $)]), filename, line); 911 assert(false); 912 } 913 914 assert(ret.type != Json.Type.undefined); 915 version(JsonLineNumbers) ret.line = curline; 916 return ret; 917 } 918 919 /** 920 Parses the given JSON string and returns the corresponding Json object. 921 922 Throws a JSONException if any parsing error occurs. 923 */ 924 Json parseJsonString(string str, string filename = null) 925 { 926 auto strcopy = str; 927 int line = 0; 928 auto ret = parseJson(strcopy, &line, filename); 929 enforceJson(strcopy.strip().length == 0, "Expected end of string after JSON value.", filename, line); 930 return ret; 931 } 932 933 unittest { 934 assert(parseJsonString("null") == Json(null)); 935 assert(parseJsonString("true") == Json(true)); 936 assert(parseJsonString("false") == Json(false)); 937 assert(parseJsonString("1") == Json(1)); 938 assert(parseJsonString("2.0") == Json(2.0)); 939 assert(parseJsonString("\"test\"") == Json("test")); 940 assert(parseJsonString("[1, 2, 3]") == Json([Json(1), Json(2), Json(3)])); 941 assert(parseJsonString("{\"a\": 1}") == Json(["a": Json(1)])); 942 assert(parseJsonString(`"\\\/\b\f\n\r\t\u1234"`).get!string == "\\/\b\f\n\r\t\u1234"); 943 auto json = parseJsonString(`{"hey": "This is @à test éhééhhéhéé !%/??*&?\ud83d\udcec"}`); 944 assert(json.toPrettyString() == parseJsonString(json.toPrettyString()).toPrettyString()); 945 } 946 947 unittest { 948 try parseJsonString(`{"a": 1`); 949 catch (Exception e) assert(e.msg.endsWith("Missing '}' before EOF.")); 950 try parseJsonString(`{"a": 1 x`); 951 catch (Exception e) assert(e.msg.endsWith("Expected '}' or ',' - got 'x'.")); 952 try parseJsonString(`[1`); 953 catch (Exception e) assert(e.msg.endsWith("Missing ']' before EOF.")); 954 try parseJsonString(`[1 x`); 955 catch (Exception e) assert(e.msg.endsWith("Expected ']' or ',' - got 'x'.")); 956 } 957 958 /** 959 Serializes the given value to JSON. 960 961 The following types of values are supported: 962 963 $(DL 964 $(DT Json) $(DD Used as-is) 965 $(DT null) $(DD Converted to Json.Type.Null) 966 $(DT bool) $(DD Converted to Json.Type.Bool) 967 $(DT float, double) $(DD Converted to Json.Type.Double) 968 $(DT short, ushort, int, uint, long, ulong) $(DD Converted to Json.Type.Int) 969 $(DT string) $(DD Converted to Json.Type.String) 970 $(DT T[]) $(DD Converted to Json.Type.Array) 971 $(DT T[string]) $(DD Converted to Json.Type.Object) 972 $(DT struct) $(DD Converted to Json.Type.Object) 973 $(DT class) $(DD Converted to Json.Type.Object or Json.Type.Null) 974 ) 975 976 All entries of an array or an associative array, as well as all R/W properties and 977 all public fields of a struct/class are recursively serialized using the same rules. 978 979 Fields ending with an underscore will have the last underscore stripped in the 980 serialized output. This makes it possible to use fields with D keywords as their name 981 by simply appending an underscore. 982 983 The following methods can be used to customize the serialization of structs/classes: 984 985 --- 986 Json toJson() const; 987 static T fromJson(Json src); 988 989 string toString() const; 990 static T fromString(string src); 991 --- 992 993 The methods will have to be defined in pairs. The first pair that is implemented by 994 the type will be used for serialization (i.e. toJson overrides toString). 995 */ 996 Json serializeToJson(T)(T value) 997 { 998 version (VibeOldSerialization) { 999 return serializeToJsonOld(value); 1000 } else { 1001 return serialize!JsonSerializer(value); 1002 } 1003 } 1004 /// ditto 1005 void serializeToJson(R, T)(R destination, T value) 1006 if (isOutputRange!(R, char) || isOutputRange!(R, ubyte)) 1007 { 1008 serialize!(JsonStringSerializer!R)(value, destination); 1009 } 1010 /// ditto 1011 string serializeToJsonString(T)(T value) 1012 { 1013 auto ret = appender!string; 1014 serializeToJson(ret, value); 1015 return ret.data; 1016 } 1017 1018 /** 1019 Serializes the given value to a pretty printed JSON string. 1020 1021 See_also: $(D serializeToJson) 1022 */ 1023 void serializeToPrettyJson(R, T)(R destination, T value) 1024 if (isOutputRange!(R, char) || isOutputRange!(R, ubyte)) 1025 { 1026 serialize!(JsonStringSerializer!(R, true))(value, destination); 1027 } 1028 /// ditto 1029 string serializeToPrettyJson(T)(T value) 1030 { 1031 auto ret = appender!string; 1032 serializeToPrettyJson(ret, value); 1033 return ret.data; 1034 } 1035 1036 /// private 1037 Json serializeToJsonOld(T)(T value) 1038 { 1039 import vson.meta.traits; 1040 1041 alias TU = Unqual!T; 1042 static if (is(TU == Json)) return value; 1043 else static if (is(TU == typeof(null))) return Json(null); 1044 else static if (is(TU == bool)) return Json(value); 1045 else static if (is(TU == float)) return Json(cast(double)value); 1046 else static if (is(TU == double)) return Json(value); 1047 else static if (is(TU == DateTime)) return Json(value.toISOExtString()); 1048 else static if (is(TU == SysTime)) return Json(value.toISOExtString()); 1049 else static if (is(TU == Date)) return Json(value.toISOExtString()); 1050 else static if (is(TU : long)) return Json(cast(long)value); 1051 else static if (is(TU : string)) return Json(value); 1052 else static if (isArray!T) { 1053 auto ret = new Json[value.length]; 1054 foreach (i; 0 .. value.length) 1055 ret[i] = serializeToJson(value[i]); 1056 return Json(ret); 1057 } else static if (isAssociativeArray!TU) { 1058 Json[string] ret; 1059 alias TK = KeyType!T; 1060 foreach (key, value; value) { 1061 static if(is(TK == string)) { 1062 ret[key] = serializeToJson(value); 1063 } else static if (is(TK == enum)) { 1064 ret[to!string(key)] = serializeToJson(value); 1065 } else static if (isStringSerializable!(TK)) { 1066 ret[key.toString()] = serializeToJson(value); 1067 } else static assert("AA key type %s not supported for JSON serialization."); 1068 } 1069 return Json(ret); 1070 } else static if (isJsonSerializable!TU) { 1071 return value.toJson(); 1072 } else static if (isStringSerializable!TU) { 1073 return Json(value.toString()); 1074 } else static if (is(TU == struct)) { 1075 Json[string] ret; 1076 foreach (m; __traits(allMembers, T)) { 1077 static if (isRWField!(TU, m)) { 1078 auto mv = __traits(getMember, value, m); 1079 ret[underscoreStrip(m)] = serializeToJson(mv); 1080 } 1081 } 1082 return Json(ret); 1083 } else static if(is(TU == class)) { 1084 if (value is null) return Json(null); 1085 Json[string] ret; 1086 foreach (m; __traits(allMembers, T)) { 1087 static if (isRWField!(TU, m)) { 1088 auto mv = __traits(getMember, value, m); 1089 ret[underscoreStrip(m)] = serializeToJson(mv); 1090 } 1091 } 1092 return Json(ret); 1093 } else static if (isPointer!TU) { 1094 if (value is null) return Json(null); 1095 return serializeToJson(*value); 1096 } else { 1097 static assert(false, "Unsupported type '"~T.stringof~"' for JSON serialization."); 1098 } 1099 } 1100 1101 1102 /** 1103 Deserializes a JSON value into the destination variable. 1104 1105 The same types as for serializeToJson() are supported and handled inversely. 1106 */ 1107 void deserializeJson(T)(ref T dst, Json src) 1108 { 1109 dst = deserializeJson!T(src); 1110 } 1111 /// ditto 1112 T deserializeJson(T)(Json src) 1113 { 1114 version (VibeOldSerialization) { 1115 return deserializeJsonOld!T(src); 1116 } else { 1117 return deserialize!(JsonSerializer, T)(src); 1118 } 1119 } 1120 /// ditto 1121 T deserializeJson(T, R)(R input) 1122 if (isInputRange!R && !is(R == Json)) 1123 { 1124 return deserialize!(JsonStringSerializer!R, T)(input); 1125 } 1126 1127 /// private 1128 T deserializeJsonOld(T)(Json src) 1129 { 1130 import vson.meta.traits; 1131 1132 static if( is(T == struct) || isSomeString!T || isIntegral!T || isFloatingPoint!T ) 1133 if( src.type == Json.Type.null_ ) return T.init; 1134 static if (is(T == Json)) return src; 1135 else static if (is(T == typeof(null))) { return null; } 1136 else static if (is(T == bool)) return src.get!bool; 1137 else static if (is(T == float)) return src.to!float; // since doubles are frequently serialized without 1138 else static if (is(T == double)) return src.to!double; // a decimal point, we allow conversions here 1139 else static if (is(T == DateTime)) return DateTime.fromISOExtString(src.get!string); 1140 else static if (is(T == SysTime)) return SysTime.fromISOExtString(src.get!string); 1141 else static if (is(T == Date)) return Date.fromISOExtString(src.get!string); 1142 else static if (is(T : long)) return cast(T)src.get!long; 1143 else static if (is(T : string)) return cast(T)src.get!string; 1144 else static if (isArray!T) { 1145 alias TV = typeof(T.init[0]) ; 1146 auto dst = new Unqual!TV[src.length]; 1147 foreach (size_t i, v; src) 1148 dst[i] = deserializeJson!(Unqual!TV)(v); 1149 return cast(T)dst; 1150 } else static if( isAssociativeArray!T ) { 1151 alias TV = typeof(T.init.values[0]) ; 1152 alias TK = KeyType!T; 1153 Unqual!TV[TK] dst; 1154 foreach (string key, value; src) { 1155 static if (is(TK == string)) { 1156 dst[key] = deserializeJson!(Unqual!TV)(value); 1157 } else static if (is(TK == enum)) { 1158 dst[to!(TK)(key)] = deserializeJson!(Unqual!TV)(value); 1159 } else static if (isStringSerializable!TK) { 1160 auto dsk = TK.fromString(key); 1161 dst[dsk] = deserializeJson!(Unqual!TV)(value); 1162 } else static assert("AA key type %s not supported for JSON serialization."); 1163 } 1164 return dst; 1165 } else static if (isJsonSerializable!T) { 1166 return T.fromJson(src); 1167 } else static if (isStringSerializable!T) { 1168 return T.fromString(src.get!string); 1169 } else static if (is(T == struct)) { 1170 T dst; 1171 foreach (m; __traits(allMembers, T)) { 1172 static if (isRWPlainField!(T, m) || isRWField!(T, m)) { 1173 alias TM = typeof(__traits(getMember, dst, m)) ; 1174 __traits(getMember, dst, m) = deserializeJson!TM(src[underscoreStrip(m)]); 1175 } 1176 } 1177 return dst; 1178 } else static if (is(T == class)) { 1179 if (src.type == Json.Type.null_) return null; 1180 auto dst = new T; 1181 foreach (m; __traits(allMembers, T)) { 1182 static if (isRWPlainField!(T, m) || isRWField!(T, m)) { 1183 alias TM = typeof(__traits(getMember, dst, m)) ; 1184 __traits(getMember, dst, m) = deserializeJson!TM(src[underscoreStrip(m)]); 1185 } 1186 } 1187 return dst; 1188 } else static if (isPointer!T) { 1189 if (src.type == Json.Type.null_) return null; 1190 alias TD = typeof(*T.init) ; 1191 dst = new TD; 1192 *dst = deserializeJson!TD(src); 1193 return dst; 1194 } else { 1195 static assert(false, "Unsupported type '"~T.stringof~"' for JSON serialization."); 1196 } 1197 } 1198 1199 unittest { 1200 import std.stdio; 1201 enum Foo : string { k = "test" } 1202 enum Boo : int { l = 5 } 1203 static struct S { float a; double b; bool c; int d; string e; byte f; ubyte g; long h; ulong i; float[] j; Foo k; Boo l; } 1204 immutable S t = {1.5, -3.0, true, int.min, "Test", -128, 255, long.min, ulong.max, [1.1, 1.2, 1.3], Foo.k, Boo.l}; 1205 S u; 1206 deserializeJson(u, serializeToJson(t)); 1207 assert(t.a == u.a); 1208 assert(t.b == u.b); 1209 assert(t.c == u.c); 1210 assert(t.d == u.d); 1211 assert(t.e == u.e); 1212 assert(t.f == u.f); 1213 assert(t.g == u.g); 1214 assert(t.h == u.h); 1215 assert(t.i == u.i); 1216 assert(t.j == u.j); 1217 assert(t.k == u.k); 1218 assert(t.l == u.l); 1219 } 1220 1221 unittest 1222 { 1223 assert(uint.max == serializeToJson(uint.max).deserializeJson!uint); 1224 assert(ulong.max == serializeToJson(ulong.max).deserializeJson!ulong); 1225 } 1226 1227 unittest { 1228 static struct A { int value; static A fromJson(Json val) { return A(val.get!int); } Json toJson() const { return Json(value); } } 1229 static struct C { int value; static C fromString(string val) { return C(val.to!int); } string toString() const { return value.to!string; } } 1230 static struct D { int value; } 1231 1232 assert(serializeToJson(const A(123)) == Json(123)); 1233 assert(serializeToJson(A(123)) == Json(123)); 1234 assert(serializeToJson(const C(123)) == Json("123")); 1235 assert(serializeToJson(C(123)) == Json("123")); 1236 assert(serializeToJson(const D(123)) == serializeToJson(["value": 123])); 1237 assert(serializeToJson(D(123)) == serializeToJson(["value": 123])); 1238 } 1239 1240 unittest { 1241 auto d = Date(2001,1,1); 1242 deserializeJson(d, serializeToJson(Date.init)); 1243 assert(d == Date.init); 1244 deserializeJson(d, serializeToJson(Date(2001,1,1))); 1245 assert(d == Date(2001,1,1)); 1246 struct S { immutable(int)[] x; } 1247 S s; 1248 deserializeJson(s, serializeToJson(S([1,2,3]))); 1249 assert(s == S([1,2,3])); 1250 struct T { 1251 @optional S s; 1252 @optional int i; 1253 @optional float f_; // underscore strip feature 1254 @optional double d; 1255 @optional string str; 1256 } 1257 auto t = T(S([1,2,3])); 1258 deserializeJson(t, parseJsonString(`{ "s" : null, "i" : null, "f" : null, "d" : null, "str" : null }`)); 1259 assert(text(t) == text(T())); 1260 } 1261 1262 unittest { 1263 static class C { 1264 int a; 1265 private int _b; 1266 @property int b() const { return _b; } 1267 @property void b(int v) { _b = v; } 1268 1269 @property int test() const { return 10; } 1270 1271 void test2() {} 1272 } 1273 C c = new C; 1274 c.a = 1; 1275 c.b = 2; 1276 1277 C d; 1278 deserializeJson(d, serializeToJson(c)); 1279 assert(c.a == d.a); 1280 assert(c.b == d.b); 1281 } 1282 1283 unittest { 1284 static struct C { int value; static C fromString(string val) { return C(val.to!int); } string toString() const { return value.to!string; } } 1285 enum Color { Red, Green, Blue } 1286 { 1287 static class T { 1288 string[Color] enumIndexedMap; 1289 string[C] stringableIndexedMap; 1290 this() { 1291 enumIndexedMap = [ Color.Red : "magenta", Color.Blue : "deep blue" ]; 1292 stringableIndexedMap = [ C(42) : "forty-two" ]; 1293 } 1294 } 1295 1296 T original = new T; 1297 original.enumIndexedMap[Color.Green] = "olive"; 1298 T other; 1299 deserializeJson(other, serializeToJson(original)); 1300 assert(serializeToJson(other) == serializeToJson(original)); 1301 } 1302 { 1303 static struct S { 1304 string[Color] enumIndexedMap; 1305 string[C] stringableIndexedMap; 1306 } 1307 1308 S *original = new S; 1309 original.enumIndexedMap = [ Color.Red : "magenta", Color.Blue : "deep blue" ]; 1310 original.enumIndexedMap[Color.Green] = "olive"; 1311 original.stringableIndexedMap = [ C(42) : "forty-two" ]; 1312 S other; 1313 deserializeJson(other, serializeToJson(original)); 1314 assert(serializeToJson(other) == serializeToJson(original)); 1315 } 1316 } 1317 1318 unittest { 1319 import std.typecons : Nullable; 1320 1321 struct S { Nullable!int a, b; } 1322 S s; 1323 s.a = 2; 1324 1325 auto j = serializeToJson(s); 1326 assert(j.a.type == Json.Type.int_); 1327 assert(j.b.type == Json.Type.null_); 1328 1329 auto t = deserializeJson!S(j); 1330 assert(!t.a.isNull() && t.a == 2); 1331 assert(t.b.isNull()); 1332 } 1333 1334 unittest { // #840 1335 int[2][2] nestedArray = 1; 1336 assert(nestedArray.serializeToJson.deserializeJson!(typeof(nestedArray)) == nestedArray); 1337 } 1338 1339 1340 /** 1341 Serializer for a plain Json representation. 1342 1343 See_Also: vibe.data.serialization.serialize, vibe.data.serialization.deserialize, serializeToJson, deserializeJson 1344 */ 1345 struct JsonSerializer { 1346 template isJsonBasicType(T) { enum isJsonBasicType = isNumeric!T || isBoolean!T || is(T == string) || is(T == typeof(null)) || isJsonSerializable!T; } 1347 1348 template isSupportedValueType(T) { enum isSupportedValueType = isJsonBasicType!T || is(T == Json); } 1349 1350 private { 1351 Json m_current; 1352 Json[] m_compositeStack; 1353 } 1354 1355 this(Json data) { m_current = data; } 1356 1357 @disable this(this); 1358 1359 // 1360 // serialization 1361 // 1362 Json getSerializedResult() { return m_current; } 1363 void beginWriteDictionary(T)() { m_compositeStack ~= Json.emptyObject; } 1364 void endWriteDictionary(T)() { m_current = m_compositeStack[$-1]; m_compositeStack.length--; } 1365 void beginWriteDictionaryEntry(T)(string name) {} 1366 void endWriteDictionaryEntry(T)(string name) { m_compositeStack[$-1][name] = m_current; } 1367 1368 void beginWriteArray(T)(size_t) { m_compositeStack ~= Json.emptyArray; } 1369 void endWriteArray(T)() { m_current = m_compositeStack[$-1]; m_compositeStack.length--; } 1370 void beginWriteArrayEntry(T)(size_t) {} 1371 void endWriteArrayEntry(T)(size_t) { m_compositeStack[$-1].appendArrayElement(m_current); } 1372 1373 void writeValue(T)(T value) 1374 { 1375 static if (is(T == Json)) m_current = value; 1376 else static if (isJsonSerializable!T) m_current = value.toJson(); 1377 else m_current = Json(value); 1378 } 1379 1380 void writeValue(T)(in Json value) if (is(T == Json)) 1381 { 1382 m_current = value.clone; 1383 } 1384 1385 // 1386 // deserialization 1387 // 1388 void readDictionary(T)(scope void delegate(string) field_handler) 1389 { 1390 enforceJson(m_current.type == Json.Type.object); 1391 auto old = m_current; 1392 foreach (string key, value; m_current) { 1393 m_current = value; 1394 field_handler(key); 1395 } 1396 m_current = old; 1397 } 1398 1399 void readArray(T)(scope void delegate(size_t) size_callback, scope void delegate() entry_callback) 1400 { 1401 enforceJson(m_current.type == Json.Type.array); 1402 auto old = m_current; 1403 size_callback(m_current.length); 1404 foreach (ent; old) { 1405 m_current = ent; 1406 entry_callback(); 1407 } 1408 m_current = old; 1409 } 1410 1411 T readValue(T)() 1412 { 1413 static if (is(T == Json)) return m_current; 1414 else static if (isJsonSerializable!T) return T.fromJson(m_current); 1415 else static if (is(T == float) || is(T == double)) { 1416 if (m_current.type == Json.Type.undefined) return T.nan; 1417 return m_current.type == Json.Type.float_ ? cast(T)m_current.get!double : cast(T)m_current.get!long; 1418 } 1419 else { 1420 return m_current.get!T(); 1421 } 1422 } 1423 1424 bool tryReadNull() { return m_current.type == Json.Type.null_; } 1425 } 1426 1427 1428 /** 1429 Serializer for a range based plain JSON string representation. 1430 1431 See_Also: vibe.data.serialization.serialize, vibe.data.serialization.deserialize, serializeToJson, deserializeJson 1432 */ 1433 struct JsonStringSerializer(R, bool pretty = false) 1434 if (isInputRange!R || isOutputRange!(R, char)) 1435 { 1436 private { 1437 R m_range; 1438 size_t m_level = 0; 1439 } 1440 1441 template isJsonBasicType(T) { enum isJsonBasicType = isNumeric!T || isBoolean!T || is(T == string) || is(T == typeof(null)) || isJsonSerializable!T; } 1442 1443 template isSupportedValueType(T) { enum isSupportedValueType = isJsonBasicType!T || is(T == Json); } 1444 1445 this(R range) 1446 { 1447 m_range = range; 1448 } 1449 1450 @disable this(this); 1451 1452 // 1453 // serialization 1454 // 1455 static if (isOutputRange!(R, char)) { 1456 private { 1457 bool m_firstInComposite; 1458 } 1459 1460 void getSerializedResult() {} 1461 1462 void beginWriteDictionary(T)() { startComposite(); m_range.put('{'); } 1463 void endWriteDictionary(T)() { endComposite(); m_range.put("}"); } 1464 void beginWriteDictionaryEntry(T)(string name) 1465 { 1466 startCompositeEntry(); 1467 m_range.put('"'); 1468 m_range.jsonEscape(name); 1469 static if (pretty) m_range.put(`": `); 1470 else m_range.put(`":`); 1471 } 1472 void endWriteDictionaryEntry(T)(string name) {} 1473 1474 void beginWriteArray(T)(size_t) { startComposite(); m_range.put('['); } 1475 void endWriteArray(T)() { endComposite(); m_range.put(']'); } 1476 void beginWriteArrayEntry(T)(size_t) { startCompositeEntry(); } 1477 void endWriteArrayEntry(T)(size_t) {} 1478 1479 void writeValue(T)(in T value) 1480 { 1481 static if (is(T == typeof(null))) m_range.put("null"); 1482 else static if (is(T == bool)) m_range.put(value ? "true" : "false"); 1483 else static if (is(T : long)) m_range.formattedWrite("%s", value); 1484 else static if (is(T : real)) m_range.formattedWrite("%.16g", value); 1485 else static if (is(T == string)) { 1486 m_range.put('"'); 1487 m_range.jsonEscape(value); 1488 m_range.put('"'); 1489 } 1490 else static if (is(T == Json)) m_range.writeJsonString(value); 1491 else static if (isJsonSerializable!T) m_range.writeJsonString!(R, pretty)(value.toJson(), m_level); 1492 else static assert(false, "Unsupported type: " ~ T.stringof); 1493 } 1494 1495 private void startComposite() 1496 { 1497 static if (pretty) m_level++; 1498 m_firstInComposite = true; 1499 } 1500 1501 private void startCompositeEntry() 1502 { 1503 if (!m_firstInComposite) { 1504 m_range.put(','); 1505 } else { 1506 m_firstInComposite = false; 1507 } 1508 static if (pretty) indent(); 1509 } 1510 1511 private void endComposite() 1512 { 1513 static if (pretty) { 1514 m_level--; 1515 if (!m_firstInComposite) indent(); 1516 } 1517 m_firstInComposite = false; 1518 } 1519 1520 private void indent() 1521 { 1522 m_range.put('\n'); 1523 foreach (i; 0 .. m_level) m_range.put('\t'); 1524 } 1525 } 1526 1527 // 1528 // deserialization 1529 // 1530 static if (isInputRange!(R)) { 1531 private { 1532 int m_line = 0; 1533 } 1534 1535 void readDictionary(T)(scope void delegate(string) entry_callback) 1536 { 1537 m_range.skipWhitespace(&m_line); 1538 enforceJson(!m_range.empty && m_range.front == '{', "Expecting object."); 1539 m_range.popFront(); 1540 bool first = true; 1541 while(true) { 1542 m_range.skipWhitespace(&m_line); 1543 enforceJson(!m_range.empty, "Missing '}'."); 1544 if (m_range.front == '}') { 1545 m_range.popFront(); 1546 break; 1547 } else if (!first) { 1548 enforceJson(m_range.front == ',', "Expecting ',' or '}', not '"~m_range.front.to!string~"'."); 1549 m_range.popFront(); 1550 m_range.skipWhitespace(&m_line); 1551 } else first = false; 1552 1553 auto name = m_range.skipJsonString(&m_line); 1554 1555 m_range.skipWhitespace(&m_line); 1556 enforceJson(!m_range.empty && m_range.front == ':'); 1557 m_range.popFront(); 1558 1559 entry_callback(name); 1560 } 1561 } 1562 1563 void readArray(T)(scope void delegate(size_t) size_callback, scope void delegate() entry_callback) 1564 { 1565 m_range.skipWhitespace(&m_line); 1566 enforceJson(!m_range.empty && m_range.front == '[', "Expecting array."); 1567 m_range.popFront(); 1568 bool first = true; 1569 while(true) { 1570 m_range.skipWhitespace(&m_line); 1571 enforceJson(!m_range.empty, "Missing ']'."); 1572 if (m_range.front == ']') { 1573 m_range.popFront(); 1574 break; 1575 } else if (!first) { 1576 enforceJson(m_range.front == ',', "Expecting ',' or ']'."); 1577 m_range.popFront(); 1578 } else first = false; 1579 1580 entry_callback(); 1581 } 1582 } 1583 1584 T readValue(T)() 1585 { 1586 m_range.skipWhitespace(&m_line); 1587 static if (is(T == typeof(null))) { enforceJson(m_range.take(4).equal("null"), "Expecting 'null'."); return null; } 1588 else static if (is(T == bool)) { 1589 bool ret = m_range.front == 't'; 1590 string expected = ret ? "true" : "false"; 1591 foreach (ch; expected) { 1592 enforceJson(m_range.front == ch, "Expecting 'true' or 'false'."); 1593 m_range.popFront(); 1594 } 1595 return ret; 1596 } else static if (is(T : long)) { 1597 bool is_float; 1598 auto num = m_range.skipNumber(is_float); 1599 enforceJson(!is_float, "Expecting integer number."); 1600 return to!T(num); 1601 } else static if (is(T : real)) { 1602 bool is_float; 1603 auto num = m_range.skipNumber(is_float); 1604 return to!T(num); 1605 } 1606 else static if (is(T == string)) return m_range.skipJsonString(&m_line); 1607 else static if (is(T == Json)) return m_range.parseJson(&m_line); 1608 else static if (isJsonSerializable!T) return T.fromJson(m_range.parseJson(&m_line)); 1609 else static assert(false, "Unsupported type: " ~ T.stringof); 1610 } 1611 1612 bool tryReadNull() 1613 { 1614 m_range.skipWhitespace(&m_line); 1615 if (m_range.front != 'n') return false; 1616 static if (is(R == string)) { 1617 import vibe.core.log; 1618 logInfo("%s", m_range[0 .. min(4, $)]); 1619 } 1620 foreach (ch; "null") { 1621 enforceJson(m_range.front == ch, "Expecting 'null'."); 1622 m_range.popFront(); 1623 } 1624 assert(m_range.empty || m_range.front != 'l'); 1625 return true; 1626 } 1627 } 1628 } 1629 1630 1631 1632 /** 1633 Writes the given JSON object as a JSON string into the destination range. 1634 1635 This function will convert the given JSON value to a string without adding 1636 any white space between tokens (no newlines, no indentation and no padding). 1637 The output size is thus minimized, at the cost of bad human readability. 1638 1639 Params: 1640 dst = References the string output range to which the result is written. 1641 json = Specifies the JSON value that is to be stringified. 1642 1643 See_Also: Json.toString, writePrettyJsonString 1644 */ 1645 void writeJsonString(R, bool pretty = false)(ref R dst, in Json json, size_t level = 0) 1646 // if( isOutputRange!R && is(ElementEncodingType!R == char) ) 1647 { 1648 final switch( json.type ){ 1649 case Json.Type.undefined: dst.put("undefined"); break; 1650 case Json.Type.null_: dst.put("null"); break; 1651 case Json.Type.bool_: dst.put(cast(bool)json ? "true" : "false"); break; 1652 case Json.Type.int_: formattedWrite(dst, "%d", json.get!long); break; 1653 case Json.Type.float_: 1654 auto d = json.get!double; 1655 if (d != d) 1656 dst.put("undefined"); // JSON has no NaN value so set null 1657 else 1658 formattedWrite(dst, "%.16g", json.get!double); 1659 break; 1660 case Json.Type..string: 1661 dst.put('\"'); 1662 jsonEscape(dst, cast(string)json); 1663 dst.put('\"'); 1664 break; 1665 case Json.Type.array: 1666 dst.put('['); 1667 bool first = true; 1668 foreach (ref const Json e; json) { 1669 if( !first ) dst.put(","); 1670 first = false; 1671 static if (pretty) { 1672 dst.put('\n'); 1673 foreach (tab; 0 .. level+1) dst.put('\t'); 1674 } 1675 if (e.type == Json.Type.undefined) dst.put("null"); 1676 else writeJsonString!(R, pretty)(dst, e, level+1); 1677 } 1678 static if (pretty) { 1679 if (json.length > 0) { 1680 dst.put('\n'); 1681 foreach (tab; 0 .. level) dst.put('\t'); 1682 } 1683 } 1684 dst.put(']'); 1685 break; 1686 case Json.Type.object: 1687 dst.put('{'); 1688 bool first = true; 1689 foreach( string k, ref const Json e; json ){ 1690 if( e.type == Json.Type.undefined ) continue; 1691 if( !first ) dst.put(','); 1692 first = false; 1693 static if (pretty) { 1694 dst.put('\n'); 1695 foreach (tab; 0 .. level+1) dst.put('\t'); 1696 } 1697 dst.put('\"'); 1698 jsonEscape(dst, k); 1699 dst.put(pretty ? `": ` : `":`); 1700 writeJsonString!(R, pretty)(dst, e, level+1); 1701 } 1702 static if (pretty) { 1703 if (json.length > 0) { 1704 dst.put('\n'); 1705 foreach (tab; 0 .. level) dst.put('\t'); 1706 } 1707 } 1708 dst.put('}'); 1709 break; 1710 } 1711 } 1712 1713 unittest { 1714 auto a = Json.emptyObject; 1715 a.a = Json.emptyArray; 1716 a.b = Json.emptyArray; 1717 a.b ~= Json(1); 1718 a.b ~= Json.emptyObject; 1719 1720 assert(a.toString() == `{"a":[],"b":[1,{}]}`); 1721 assert(a.toPrettyString() == 1722 `{ 1723 "a": [], 1724 "b": [ 1725 1, 1726 {} 1727 ] 1728 }`); 1729 } 1730 1731 unittest { // #735 1732 auto a = Json.emptyArray; 1733 a ~= "a"; 1734 a ~= Json(); 1735 a ~= "b"; 1736 a ~= null; 1737 a ~= "c"; 1738 assert(a.toString() == `["a",null,"b",null,"c"]`); 1739 } 1740 1741 unittest { 1742 auto a = Json.emptyArray; 1743 a ~= Json(1); 1744 a ~= Json(2); 1745 a ~= Json(3); 1746 a ~= Json(4); 1747 a ~= Json(5); 1748 1749 auto b = Json(a[0..a.length]); 1750 assert(a == b); 1751 1752 auto c = Json(a[0..$]); 1753 assert(a == c); 1754 assert(b == c); 1755 1756 auto d = [Json(1),Json(2),Json(3)]; 1757 assert(d == a[0..a.length-2]); 1758 assert(d == a[0..$-2]); 1759 } 1760 1761 unittest { 1762 auto j = Json(double.init); 1763 1764 assert(j.toString == "undefined"); // A double nan should serialize to undefined 1765 j = 17.04f; 1766 assert(j.toString == "17.04"); // A proper double should serialize correctly 1767 1768 double d; 1769 deserializeJson(d, Json.undefined); // Json.undefined should deserialize to nan 1770 assert(d != d); 1771 } 1772 /** 1773 Writes the given JSON object as a prettified JSON string into the destination range. 1774 1775 The output will contain newlines and indents to make the output human readable. 1776 1777 Params: 1778 dst = References the string output range to which the result is written. 1779 json = Specifies the JSON value that is to be stringified. 1780 level = Specifies the base amount of indentation for the output. Indentation is always 1781 done using tab characters. 1782 1783 See_Also: Json.toPrettyString, writeJsonString 1784 */ 1785 void writePrettyJsonString(R)(ref R dst, in Json json, int level = 0) 1786 // if( isOutputRange!R && is(ElementEncodingType!R == char) ) 1787 { 1788 writeJsonString!(R, true)(dst, json, level); 1789 } 1790 1791 1792 /** 1793 Helper function that escapes all Unicode characters in a JSON string. 1794 */ 1795 string convertJsonToASCII(string json) 1796 { 1797 auto ret = appender!string; 1798 jsonEscape!true(ret, json); 1799 return ret.data; 1800 } 1801 1802 1803 /// private 1804 private void jsonEscape(bool escape_unicode = false, R)(ref R dst, string s) 1805 { 1806 for (size_t pos = 0; pos < s.length; pos++) { 1807 immutable(char) ch = s[pos]; 1808 1809 switch (ch) { 1810 default: 1811 static if (escape_unicode) { 1812 if (ch > 0x20 && ch < 0x80) dst.put(ch); 1813 else { 1814 import std.utf : decode; 1815 char[13] buf; 1816 int len; 1817 dchar codepoint = decode(s, pos); 1818 import std.c.stdio : sprintf; 1819 /* codepoint is in BMP */ 1820 if(codepoint < 0x10000) 1821 { 1822 sprintf(&buf[0], "\\u%04X", codepoint); 1823 len = 6; 1824 } 1825 /* not in BMP -> construct a UTF-16 surrogate pair */ 1826 else 1827 { 1828 int first, last; 1829 1830 codepoint -= 0x10000; 1831 first = 0xD800 | ((codepoint & 0xffc00) >> 10); 1832 last = 0xDC00 | (codepoint & 0x003ff); 1833 1834 sprintf(&buf[0], "\\u%04X\\u%04X", first, last); 1835 len = 12; 1836 } 1837 1838 pos -= 1; 1839 foreach (i; 0 .. len) 1840 dst.put(buf[i]); 1841 1842 } 1843 } else { 1844 if (ch < 0x20) dst.formattedWrite("\\u%04X", ch); 1845 else dst.put(ch); 1846 } 1847 break; 1848 case '\\': dst.put("\\\\"); break; 1849 case '\r': dst.put("\\r"); break; 1850 case '\n': dst.put("\\n"); break; 1851 case '\t': dst.put("\\t"); break; 1852 case '\"': dst.put("\\\""); break; 1853 } 1854 } 1855 } 1856 1857 /// private 1858 private string jsonUnescape(R)(ref R range) 1859 { 1860 auto ret = appender!string(); 1861 while(!range.empty){ 1862 auto ch = range.front; 1863 switch( ch ){ 1864 case '"': return ret.data; 1865 case '\\': 1866 range.popFront(); 1867 enforceJson(!range.empty, "Unterminated string escape sequence."); 1868 switch(range.front){ 1869 default: enforceJson(false, "Invalid string escape sequence."); break; 1870 case '"': ret.put('\"'); range.popFront(); break; 1871 case '\\': ret.put('\\'); range.popFront(); break; 1872 case '/': ret.put('/'); range.popFront(); break; 1873 case 'b': ret.put('\b'); range.popFront(); break; 1874 case 'f': ret.put('\f'); range.popFront(); break; 1875 case 'n': ret.put('\n'); range.popFront(); break; 1876 case 'r': ret.put('\r'); range.popFront(); break; 1877 case 't': ret.put('\t'); range.popFront(); break; 1878 case 'u': 1879 1880 dchar decode_unicode_escape() { 1881 enforceJson(range.front == 'u'); 1882 range.popFront(); 1883 dchar uch = 0; 1884 foreach( i; 0 .. 4 ){ 1885 uch *= 16; 1886 enforceJson(!range.empty, "Unicode sequence must be '\\uXXXX'."); 1887 auto dc = range.front; 1888 range.popFront(); 1889 1890 if( dc >= '0' && dc <= '9' ) uch += dc - '0'; 1891 else if( dc >= 'a' && dc <= 'f' ) uch += dc - 'a' + 10; 1892 else if( dc >= 'A' && dc <= 'F' ) uch += dc - 'A' + 10; 1893 else enforceJson(false, "Unicode sequence must be '\\uXXXX'."); 1894 } 1895 return uch; 1896 } 1897 1898 auto uch = decode_unicode_escape(); 1899 1900 if(0xD800 <= uch && uch <= 0xDBFF) { 1901 /* surrogate pair */ 1902 range.popFront(); // backslash '\' 1903 auto uch2 = decode_unicode_escape(); 1904 enforceJson(0xDC00 <= uch2 && uch2 <= 0xDFFF, "invalid Unicode"); 1905 { 1906 /* valid second surrogate */ 1907 uch = 1908 ((uch - 0xD800) << 10) + 1909 (uch2 - 0xDC00) + 1910 0x10000; 1911 } 1912 } 1913 ret.put(uch); 1914 break; 1915 } 1916 break; 1917 default: 1918 ret.put(ch); 1919 range.popFront(); 1920 break; 1921 } 1922 } 1923 return ret.data; 1924 } 1925 1926 /// private 1927 private string skipNumber(R)(ref R s, out bool is_float) 1928 { 1929 // TODO: make this work with input ranges 1930 size_t idx = 0; 1931 is_float = false; 1932 if (s[idx] == '-') idx++; 1933 if (s[idx] == '0') idx++; 1934 else { 1935 enforceJson(isDigit(s[idx++]), "Digit expected at beginning of number."); 1936 while( idx < s.length && isDigit(s[idx]) ) idx++; 1937 } 1938 1939 if( idx < s.length && s[idx] == '.' ){ 1940 idx++; 1941 is_float = true; 1942 while( idx < s.length && isDigit(s[idx]) ) idx++; 1943 } 1944 1945 if( idx < s.length && (s[idx] == 'e' || s[idx] == 'E') ){ 1946 idx++; 1947 is_float = true; 1948 if( idx < s.length && (s[idx] == '+' || s[idx] == '-') ) idx++; 1949 enforceJson( idx < s.length && isDigit(s[idx]), "Expected exponent." ~ s[0 .. idx]); 1950 idx++; 1951 while( idx < s.length && isDigit(s[idx]) ) idx++; 1952 } 1953 1954 string ret = s[0 .. idx]; 1955 s = s[idx .. $]; 1956 return ret; 1957 } 1958 1959 /// private 1960 private string skipJsonString(R)(ref R s, int* line = null) 1961 { 1962 // TODO: count or disallow any newlines inside of the string 1963 enforceJson(!s.empty && s.front == '"', "Expected '\"' to start string."); 1964 s.popFront(); 1965 string ret = jsonUnescape(s); 1966 enforceJson(!s.empty && s.front == '"', "Expected '\"' to terminate string."); 1967 s.popFront(); 1968 return ret; 1969 } 1970 1971 /// private 1972 private void skipWhitespace(R)(ref R s, int* line = null) 1973 { 1974 while (!s.empty) { 1975 switch (s.front) { 1976 default: return; 1977 case ' ', '\t': s.popFront(); break; 1978 case '\n': 1979 s.popFront(); 1980 if (!s.empty && s.front == '\r') s.popFront(); 1981 if (line) (*line)++; 1982 break; 1983 case '\r': 1984 s.popFront(); 1985 if (!s.empty && s.front == '\n') s.popFront(); 1986 if (line) (*line)++; 1987 break; 1988 } 1989 } 1990 } 1991 1992 private bool isDigit(dchar ch) { return ch >= '0' && ch <= '9'; } 1993 1994 private string underscoreStrip(string field_name) 1995 { 1996 if( field_name.length < 1 || field_name[$-1] != '_' ) return field_name; 1997 else return field_name[0 .. $-1]; 1998 } 1999 2000 /// private 2001 package template isJsonSerializable(T) { enum isJsonSerializable = is(typeof(T.init.toJson()) == Json) && is(typeof(T.fromJson(Json())) == T); } 2002 2003 private void enforceJson(string file = __FILE__, size_t line = __LINE__)(bool cond, lazy string message = "JSON exception") 2004 { 2005 static if (__VERSION__ >= 2065) enforceEx!JSONException(cond, message, file, line); 2006 else if (!cond) throw new JSONException(message); 2007 } 2008 2009 private void enforceJson(string file = __FILE__, size_t line = __LINE__)(bool cond, lazy string message, string err_file, int err_line) 2010 { 2011 auto errmsg = format("%s(%s): Error: %s", err_file, err_line+1, message); 2012 static if (__VERSION__ >= 2065) enforceEx!JSONException(cond, errmsg, file, line); 2013 else if (!cond) throw new JSONException(errmsg); 2014 } 2015 2016 private void enforceJson(string file = __FILE__, size_t line = __LINE__)(bool cond, lazy string message, string err_file, int* err_line) 2017 { 2018 enforceJson!(file, line)(cond, message, err_file, err_line ? *err_line : -1); 2019 }