1 /** 2 * Defines a generic value type for builing and holding JSON documents in memory. 3 * 4 * Copyright: Copyright 2012 - 2015, Sönke Ludwig. 5 * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 6 * Authors: Sönke Ludwig 7 * Source: $(PHOBOSSRC std/data/json/value.d) 8 */ 9 module funkwerk.stdx.data.json.value; 10 @safe: 11 12 /// 13 unittest { 14 // build a simple JSON document 15 auto aa = ["a": JSONValue("hello"), "b": JSONValue(true)]; 16 auto obj = JSONValue(aa); 17 18 // JSONValue behaves almost as the contained native D types 19 assert(obj["a"] == "hello"); 20 assert(obj["b"] == true); 21 } 22 23 import funkwerk.stdx.data.json.foundation; 24 import std.typecons : Nullable; 25 import funkwerk.stdx.data.json.taggedalgebraic; 26 27 28 /** 29 * Represents a generic JSON value. 30 * 31 * The $(D JSONValue) type is based on $(D std.variant.Algebraic) and as such 32 * provides the usual binary and unary operators for handling the contained 33 * raw value. 34 * 35 * Raw values can be either $(D null), $(D bool), $(D double), $(D string), 36 * $(D JSONValue[]) or $(D JSONValue[string]). 37 */ 38 struct JSONValue 39 { 40 import std.exception : enforce; 41 import funkwerk.stdx.data.json.lexer : JSONToken; 42 43 /** 44 * Defines the possible types contained in a `JSONValue` 45 */ 46 union PayloadUnion { 47 typeof(null) null_; /// A JSON `null` value 48 bool boolean; /// JSON `true` or `false` values 49 double double_; /// The default field for storing numbers 50 long integer; /// Only used if `LexOptions.useLong` was set for parsing 51 WrappedBigInt bigInt; /// Only used if `LexOptions.useBigInt` was set for parsing 52 @disableIndex .string string; /// String value 53 JSONValue[] array; /// Array or JSON values 54 JSONValue[.string] object; /// Dictionary of JSON values (object) 55 } 56 57 /** 58 * Alias for a $(D TaggedAlgebraic) able to hold all possible JSON 59 * value types. 60 */ 61 alias Payload = TaggedAlgebraic!PayloadUnion; 62 63 /** 64 * Holds the data contained in this value. 65 * 66 * Note that this is available using $(D alias this), so there is usually no 67 * need to access this field directly. 68 */ 69 Payload payload; 70 71 /** 72 * Optional location of the corresponding token in the source document. 73 * 74 * This field will be automatically populated by the JSON parser if location 75 * tracking is enabled. 76 */ 77 Location location; 78 79 /// 80 alias payload this; 81 82 /** 83 * Constructs a JSONValue from the given raw value. 84 */ 85 this(T)(T value, Location loc = Location.init) { payload = Payload(value); location = loc; } 86 /// ditto 87 void opAssign(T)(T value) { payload = value; } 88 89 /// Tests if the stored value is of a given type. 90 bool hasType(T)() const { return .hasType!T(payload); } 91 92 /// Tests if the stored value is of kind `Kind.null_`. 93 bool isNull() const { return payload.kind == Kind.null_; } 94 95 /** 96 * Returns the raw contained value. 97 * 98 * This must only be called if the type of the stored value matches `T`. 99 * Use `.hasType!T` or `.typeID` for that purpose. 100 */ 101 ref inout(T) get(T)() inout { return .get!T(payload); } 102 103 /** 104 * Enables equality comparisons. 105 * 106 * Note that the location is considered token metadata and thus does not 107 * affect the comparison. 108 */ 109 bool opEquals(T)(auto ref inout(T) other) inout 110 { 111 import std.traits : Unqual; 112 113 static if (is(Unqual!T == typeof(null))) 114 { 115 return this.isNull; 116 } 117 else static if (is(Unqual!T == JSONValue)) 118 { 119 return this.payload == other.payload; 120 } 121 else 122 { 123 return this.payload == other; 124 } 125 } 126 } 127 128 /// Shows the basic construction and operations on JSON values. 129 unittest 130 { 131 JSONValue a = 12; 132 JSONValue b = 13; 133 134 assert(a == 12.0); 135 assert(b == 13.0); 136 assert(a + b == 25.0); 137 138 auto c = JSONValue([a, b]); 139 assert(c[0] == 12.0); 140 assert(c[1] == 13.0); 141 assert(c[0] == a); 142 assert(c[1] == b); 143 144 auto d = JSONValue(["a": a, "b": b]); 145 assert(d["a"] == 12.0); 146 assert(d["b"] == 13.0); 147 assert(d["a"] == a); 148 assert(d["b"] == b); 149 } 150 151 // Unittests for JSONValue equality comparisons 152 unittest 153 { 154 JSONValue nullval = null; 155 assert(nullval.hasType!(typeof(null))()); 156 assert(nullval == null); 157 assert(nullval == nullval); 158 159 JSONValue boolval = true; 160 assert(boolval.hasType!bool()); 161 assert(boolval == true); 162 assert(boolval == boolval); 163 164 JSONValue intval = 22; 165 assert(intval.hasType!long()); 166 assert(intval == 22); 167 assert(intval == 22.0); 168 assert(intval == intval); 169 170 JSONValue longval = 56L; 171 assert(longval.hasType!long()); 172 assert(longval == 56); 173 assert(longval == 56.0); 174 assert(longval == longval); 175 176 assert(intval + longval == 78); 177 assert(intval + longval == intval + longval); 178 179 JSONValue floatval = 32.0f; 180 assert(floatval.hasType!double()); 181 assert(floatval == 32); 182 assert(floatval == 32.0); 183 assert(floatval == floatval); 184 185 JSONValue doubleval = 63.5; 186 assert(doubleval.hasType!double()); 187 assert(doubleval == 63.5); 188 assert(doubleval == doubleval); 189 190 assert(floatval + doubleval == 95.5); 191 assert(floatval + doubleval == floatval + doubleval); 192 assert(intval + longval + floatval + doubleval == 173.5); 193 assert(intval + longval + floatval + doubleval == 194 intval + longval + floatval + doubleval); 195 196 JSONValue strval = "Hello!"; 197 assert(strval.hasType!string()); 198 assert(strval == "Hello!"); 199 assert(strval == strval); 200 201 auto arrval = JSONValue([floatval, doubleval]); 202 assert(arrval.hasType!(JSONValue[])()); 203 assert(arrval == [floatval, doubleval]); 204 assert(arrval == [32.0, 63.5]); 205 assert(arrval[0] == floatval); 206 assert(arrval[0] == 32.0); 207 assert(arrval[1] == doubleval); 208 assert(arrval[1] == 63.5); 209 assert(arrval == arrval); 210 211 auto objval = JSONValue(["f": floatval, "d": doubleval]); 212 assert(objval.hasType!(JSONValue[string])()); 213 assert(objval["f"] == floatval); 214 assert(objval["f"] == 32.0); 215 assert(objval["d"] == doubleval); 216 assert(objval["d"] == 63.5); 217 assert(objval == objval); 218 } 219 220 221 /// Proxy structure that stores BigInt as a pointer to save space in JSONValue 222 static struct WrappedBigInt { 223 import std.bigint; 224 private BigInt* _pvalue; 225 /// 226 this(BigInt value) { _pvalue = new BigInt(value); } 227 /// 228 @property ref inout(BigInt) value() inout { return *_pvalue; } 229 } 230 231 232 /** 233 * Allows safe access of sub paths of a `JSONValue`. 234 * 235 * Missing intermediate values will not cause an error, but will instead 236 * just cause the final path node to be marked as non-existent. See the 237 * example below for the possbile use cases. 238 */ 239 auto opt()(auto ref JSONValue val) 240 { 241 alias C = JSONValue; // this function is generic and could also operate on BSONValue or similar types 242 static struct S(F...) { 243 private { 244 static if (F.length > 0) { 245 S!(F[0 .. $-1])* _parent; 246 F[$-1] _field; 247 } 248 else 249 { 250 C* _container; 251 } 252 } 253 254 static if (F.length == 0) 255 { 256 this(ref C container) 257 { 258 () @trusted { _container = &container; } (); 259 } 260 } 261 else 262 { 263 this (ref S!(F[0 .. $-1]) s, F[$-1] field) 264 { 265 () @trusted { _parent = &s; } (); 266 _field = field; 267 } 268 } 269 270 @disable this(); // don't let the reference escape 271 272 @property bool exists() const { return resolve !is null; } 273 274 inout(JSONValue) get() inout 275 { 276 auto val = this.resolve(); 277 if (val is null) 278 throw new .Exception("Missing JSON value at "~this.path()~"."); 279 return *val; 280 } 281 282 inout(T) get(T)(T def_value) inout 283 { 284 auto val = resolve(); 285 if (val is null || !val.hasType!T) 286 return def_value; 287 return val.get!T; 288 } 289 290 alias get this; 291 292 @property auto opDispatch(string name)() 293 { 294 return S!(F, string)(this, name); 295 } 296 297 @property void opDispatch(string name, T)(T value) 298 { 299 (*resolveWrite(OptWriteMode.dict))[name] = value; 300 } 301 302 auto opIndex()(size_t idx) 303 { 304 return S!(F, size_t)(this, idx); 305 } 306 307 auto opIndex()(string name) 308 { 309 return S!(F, string)(this, name); 310 } 311 312 auto opIndexAssign(T)(T value, size_t idx) 313 { 314 *(this[idx].resolveWrite(OptWriteMode.any)) = value; 315 } 316 317 auto opIndexAssign(T)(T value, string name) 318 { 319 (*resolveWrite(OptWriteMode.dict))[name] = value; 320 } 321 322 private inout(C)* resolve() 323 inout { 324 static if (F.length > 0) 325 { 326 auto c = this._parent.resolve(); 327 if (!c) return null; 328 static if (is(F[$-1] : long)) { 329 if (!c.hasType!(C[])) return null; 330 if (_field < c.length) return &c.get!(C[])[_field]; 331 return null; 332 } 333 else 334 { 335 if (!c.hasType!(C[string])) return null; 336 return this._field in *c; 337 } 338 } 339 else 340 { 341 return _container; 342 } 343 } 344 345 private C* resolveWrite(OptWriteMode mode) 346 { 347 C* v; 348 static if (F.length == 0) 349 { 350 v = _container; 351 } 352 else 353 { 354 auto c = _parent.resolveWrite(is(F[$-1] == string) ? OptWriteMode.dict : OptWriteMode.array); 355 static if (is(F[$-1] == string)) 356 { 357 v = _field in *c; 358 if (!v) 359 { 360 (*c)[_field] = mode == OptWriteMode.dict ? C(cast(C[string])null) : C(cast(C[])null); 361 v = _field in *c; 362 } 363 } 364 else 365 { 366 import std.conv : to; 367 if (_field >= c.length) 368 throw new Exception("Array index "~_field.to!string()~" out of bounds ("~c.length.to!string()~") for "~_parent.path()~"."); 369 v = &c.get!(C[])[_field]; 370 } 371 } 372 373 final switch (mode) 374 { 375 case OptWriteMode.dict: 376 if (!v.hasType!(C[string])) 377 throw new .Exception(pname()~" is not a dictionary/object. Cannot set a field."); 378 break; 379 case OptWriteMode.array: 380 if (!v.hasType!(C[])) 381 throw new .Exception(pname()~" is not an array. Cannot set an entry."); 382 break; 383 case OptWriteMode.any: break; 384 } 385 386 return v; 387 } 388 389 private string path() const 390 { 391 static if (F.length > 0) 392 { 393 import std.conv : to; 394 static if (is(F[$-1] == string)) return this._parent.path() ~ "." ~ this._field; 395 else return this._parent.path() ~ "[" ~ this._field.to!string ~ "]"; 396 } 397 else 398 { 399 return ""; 400 } 401 } 402 403 private string pname() const 404 { 405 static if (F.length > 0) return "Field "~_parent.path(); 406 else return "Value"; 407 } 408 } 409 410 return S!()(val); 411 } 412 413 /// 414 unittest 415 { 416 import std.exception : assertThrown; 417 418 JSONValue subobj = ["b": JSONValue(1.0), "c": JSONValue(2.0)]; 419 JSONValue subarr = [JSONValue(3.0), JSONValue(4.0), JSONValue(null)]; 420 JSONValue obj = ["a": subobj, "b": subarr]; 421 422 // access nested fields using member access syntax 423 assert(opt(obj).a.b == 1.0); 424 assert(opt(obj).a.c == 2.0); 425 426 // get can be used with a default value 427 assert(opt(obj).a.c.get(-1.0) == 2.0); // matched path and type 428 assert(opt(obj).a.c.get(null) == null); // mismatched type -> return default value 429 assert(opt(obj).a.d.get(-1.0) == -1.0); // mismatched path -> return default value 430 431 // explicit existence check 432 assert(!opt(obj).x.exists); 433 assert(!opt(obj).a.x.y.exists); // works for nested missing paths, too 434 435 // instead of using member syntax, index syntax can be used 436 assert(opt(obj)["a"]["b"] == 1.0); 437 438 // integer indices work, too 439 assert(opt(obj).b[0] == 3.0); 440 assert(opt(obj).b[1] == 4.0); 441 assert(opt(obj).b[2].exists); 442 assert(opt(obj).b[2] == null); 443 assert(!opt(obj).b[3].exists); 444 445 // accessing a missing path throws an exception 446 assertThrown(opt(obj).b[3] == 3); 447 448 // assignments work, too 449 opt(obj).b[0] = 12; 450 assert(opt(obj).b[0] == 12); 451 452 // assignments to non-existent paths automatically create all missing parents 453 opt(obj).c.d.opDispatch!"e"( 12); 454 assert(opt(obj).c.d.e == 12); 455 456 // writing to paths with conflicting types will throw 457 assertThrown(opt(obj).c[2] = 12); 458 459 // writing out of array bounds will also throw 460 assertThrown(opt(obj).b[10] = 12); 461 } 462 463 private enum OptWriteMode { 464 dict, 465 array, 466 any 467 }