Function opt

Allows safe access of sub paths of a JSONValue.

auto auto opt (
  auto ref JSONValue val
);

Missing intermediate values will not cause an error, but will instead just cause the final path node to be marked as non-existent. See the example below for the possbile use cases.

Example

import std.exception : assertThrown;

JSONValue subobj = ["b": JSONValue(1.0), "c": JSONValue(2.0)];
JSONValue subarr = [JSONValue(3.0), JSONValue(4.0), JSONValue(null)];
JSONValue obj = ["a": subobj, "b": subarr];

// access nested fields using member access syntax
assert(opt(obj).a.b == 1.0);
assert(opt(obj).a.c == 2.0);

// get can be used with a default value
assert(opt(obj).a.c.get(-1.0) == 2.0); // matched path and type
assert(opt(obj).a.c.get(null) == null); // mismatched type -> return default value
assert(opt(obj).a.d.get(-1.0) == -1.0); // mismatched path -> return default value

// explicit existence check
assert(!opt(obj).x.exists);
assert(!opt(obj).a.x.y.exists); // works for nested missing paths, too

// instead of using member syntax, index syntax can be used
assert(opt(obj)["a"]["b"] == 1.0);

// integer indices work, too
assert(opt(obj).b[0] == 3.0);
assert(opt(obj).b[1] == 4.0);
assert(opt(obj).b[2].exists);
assert(opt(obj).b[2] == null);
assert(!opt(obj).b[3].exists);

// accessing a missing path throws an exception
assertThrown(opt(obj).b[3] == 3);

// assignments work, too
opt(obj).b[0] = 12;
assert(opt(obj).b[0] == 12);

// assignments to non-existent paths automatically create all missing parents
opt(obj).c.d.opDispatch!"e"( 12);
assert(opt(obj).c.d.e == 12);

// writing to paths with conflicting types will throw
assertThrown(opt(obj).c[2] = 12);

// writing out of array bounds will also throw
assertThrown(opt(obj).b[10] = 12);