Crate json_ops

source ·
Expand description

An alternative and may convenient way to operate on untyped json is provided through operator overloading based on json pointer. The core start point is path operator / to create a json pointer struct, which is no more than Option<&Value> that points to a node in json tree.

use serde_json::json;
use json_ops::ValuePath;

let v = json!({"root": true, "usr": {
    "include": ["a.h", "b.h"],
    "lib": ["a.so", "b.so", {"name": "c.so", "version": "0.0.1"}],
    "lib64": null,
    "local": {"include": [], "lib": null}}
});

let p1 = v.path() / "usr" / "lib" / 2 / "name";
let p2 = v.path() / "usr/lib/2/name";
let p3 = v.path() / "/usr/lib/2/name";
let p4 = v.pointer("/usr/lib/2/name");
assert!(p1 == p2 && p2 == p3 && p3.unwrap() == p4.unwrap());
assert!(v.path().unwrap() == &v);

let usr = v.path() / "usr";
let local = v.path() / "usr" / "local";
let lib = "lib64";
let p1 = usr / lib;
let p2 = local / lib;

assert!(p1.is_some());
assert!(p1.unwrap().is_null());
assert!(p2.is_none());
assert!(v["usr"]["local"]["lib64 or any absent"].is_null());

There is another struct for mutable json pointer as well from path_mut().

The difference between / operator and pointer() method:

  • operator / can be chained and manually split path token in compile time.
  • each path token can be use variable and modify seperately.
  • joined path can omit the leading / required by json pointer syntax.
  • split path no need to escpace special char when key contins / or ~.
  • easy to save middle node pointer as variable and reuse later.

The difference between / operator and [] index:

  • a bit more consistent and compact.
  • can distinguish json null node and non-exist node.
  • mutable pointer won’t auto insert key to json object as index does.
  • mutable pointer won’t panic when beyond range of json array as index does.

The pointer struct can further use operator | to read the primitive value held in node with default fallback, and operator << to overwrite leaf node or push new item to array or object node.

use serde_json::json;
use json_ops::ValuePath;

let mut v = json!({"int":10, "float":3.14, "array":["pi", null, true]});

let node = v.path() / "int";
let val = node | 0;
assert_eq!(val, 10);
assert_eq!(v.path() / "float" | 0.0, 3.14);

let _ode = v.path_mut() / "float" << 31.4;
let node = v.path_mut() / "array" / 2 << "true";
assert!(node.as_ref().unwrap().is_string()); // changed node type

let _ode = v.path_mut() << ("key", "val");
let _ode = v.path_mut() / "array" << ("val",) << ["more"] << [100];
let _ode = v.path_mut() / "int" << ();
assert!(v["int"].is_null());

assert_eq!(v, json!({"int":null, "float":31.4, "key":"val", "array":["pi",null,"true","val","more",100]}));

When enable toml feature, then toml pointer can be used as the same as json.

Structs

  • Wrap Option<&Value> as pointer to json node for operator overload.
  • Mutable josn pointer wrapper of Optione<&mut Value> for operator overload.

Traits

  • The rust type for scalar json node, which can used after operator | to read, or/and operator << to write. Only support i64 for integer, to make use literal number more convenient.
  • Yield json (or more generic value) pointer to support operator / overload. All methods have defualt implementation, only override as needed. Usually the get_* methods are required to implement for specific type, while the path* methods are good enough to use directly.
  • Extend method to read Value, and support operator | rhs_default. The default implementation just return rhs without any treatment. It is dependent for concrete Value type how extract value from node.
  • Extend method to read Value, and support operator << rhs. The default implementation just return self without any modification. It is dependent for concrete Value type how modify the lhs node.