query_value

Macro query_value 

Source
macro_rules! query_value {
    ($(mut)? $value:tt $(query:tt)* $(?? $default:expr)?) => { ... };
    ($(mut)? $value:tt $(query:tt)* -> $as:ident $(?? $default:expr)?) => { ... };
    ($(mut)? $value:tt $(query:tt)* >> ($deser_to:ty) $(?? $default:expr)?) => { ... };
}
Expand description

A macro for querying an inner value of a structured (“JSON-ish”) data.

use valq::query_value;

// let obj = json!({ ... });
// let arr = json!([ ... ]);

// get the field `foo` from the JSON-ish object `obj`
let foo: Option<&Value> = query_value!(obj.foo);

// get the first item of the nested JSON array `arr` in `obj`
let head = query_value!(obj.arr[0]);

// more complex query, just works!
let abyss = query_value!(obj.path.to.matrix[0][1].abyss);

§Query Notations

You can traverse through semi-structured (“JSON-ish”) data by chaining JavaScript-like accessor notaions:

  • Dot notation (.field): Access a property of an “object” (key-value structure) by name
  • Bracket notation (["field"]): Access a property of an “object”, or an element of an “array”-like value by index
    • With string index, you get access to object properties, similar to dot notation. This is especially useful for keys that are not valid Rust identifiers (e.g. "1st", "foo-bar").
    • With integer index, you get access to array elements.
    • Dynamic query: you can place a Rust expression that evaluate to string or integer in the brackets.

§Query Result

query_value! returns an Option as the result of the query (except when unwrapping operator ?? is used; see below for details).

Queries can fail for the following reasons. In that case, query_value! returns None:

  • The specified key or index does not exist in the target value
  • Indexing an object (key-value structure) with an integer
  • Indexing an array with a string key

Otherwise, i.e. if your query succeeds, query_value! returns the queried value wrapped in Some.

With basic queries, query_value! extracts a shared reference (&) to the inner value by default. Think of it as a function that has following signature:

query_value!(query...) -> Option(&Value)

§mut: Extracting Mutable Reference to Inner Value

Queries start with mut extract the mutable reference (&mut) to the inner value instead:

query_value!(mut query...) -> Option(&mut Value)

Example:

use serde_json::{json, Value};
use valq::query_value;

let mut obj = json!({"foo": { "bar": { "x": 1, "y": 2 }}});
{
    let bar: &mut Value = query_value!(mut obj.foo.bar).unwrap();
    *bar = json!({"x": 100, "y": 200});
}
// see below for `->` syntax
assert_eq!(query_value!(obj.foo.bar.x -> u64), Some(100));
assert_eq!(query_value!(obj.foo.bar.y -> u64), Some(200));

§->: Cast Value with as_***()

Queries end with -> *** try to cast the extracted value with as_***() method. In the mut context, as_***_mut() method is used instead.

// assuming your value has the method `as_str(&self) -> Option(&str)`
query_value!(query... -> str) -> Option(&str)

// assuming your value has the method `as_array_mut(&mut self) -> Option(&mut Vec<Value>)`
query_value!(mut query... -> array) -> Option(&mut Vec<Value>)
use serde_json::{json, Value};
use valq::query_value;

let mut obj = json!({"foo": "hello", "arr": [1, 2]});

// try to cast extracted value with `as_u64` method on that value
// results in `None` in case of type mismatch
let foo_str: Option<&str> = query_value!(obj.foo -> str);
assert_eq!(foo_str, Some("hello"));

// `mut` example
let arr_vec: Option<&mut Vec<Value>> = query_value!(mut obj.arr -> array);
assert_eq!(arr_vec, Some(&mut vec![json!(1), json!(2)]));

§>>: Deserializing Value into Any Types Implement serde::Deserialize trait

Queries end with >> (Type) try to deserialize the extracted value using deserialize() method on the Type; i.e. you can get a value of your Type out of the queried value, assuming the Type implements serde::Deserialize.

// assuming `Type` implements `serde::Deserialize`
query_value!(query... >> (Type)) -> Option(Type)
use serde::Deserialize;
use serde_json::json;
use valq::query_value;

#[derive(Debug, PartialEq, Deserialize)]
struct Person {
    name: String,
    age: u8,
}

let j = json!({"author": {"name": "jiftechnify", "age": 31}});
assert_eq!(
    query_value!(j.author >> (Person)),
    Some(Person {
        name: "jiftechnify".into(),
        age: 31u8,
    }),
);

A few notes on the >> operator:

  • Basically, the type name after >> must be wrapped with parentheses. As a special case, you can omit that parens only if your type name consists of a single identifier, for simplicity.
    • For example, the query above can be simplified to j.author >> Person.
  • Deserialization with >> involves cloning of the queried value. You may want to use -> type casting if possible.

§??: Unwarp Query Result with Default Value

You put ?? ... at the very end of the query to unwrap the query result with providing a default value in case of query failure.

  • ?? <expr>: Use the value of<expr> as the default.
  • ?? default: Use Default::default() as the default.

This is especilly useful together with -> or >> conversions:

use serde_json::{json, Value};
use valq::query_value;

let obj = json!({"foo": {"bar": "not a number"}});
assert_eq!(query_value!(obj.foo.bar -> str ?? "failed!"), "not a number");
assert_eq!(query_value!(obj.foo.bar -> u64 ?? 42), 42);
assert_eq!(query_value!(obj.foo.bar -> u64 ?? default), 0u64); // u64::default()

§Query Syntax Specification

query_value!(
    ("mut")?
    <value> ("." <key> | "[" <idx> "]")*
    ("->" <as_dest> | ">>" "(" <deser_dest> ")")?
    ("??" ("default" | <default_expr>))?
)

where:

  • <value>: An expression evaluates to a structured data to be queried
  • <key>: A property/field key to extract value from a key-value structure
  • <idx>: An index to extract value from structure
    • For an array-like structure, any expressions evaluates to an integer can be used
    • For a key-value structure, any expressions evaluates to a string can be used
  • <as_dest>: A destination type of type casting with as_***() / as_***_mut() methods
  • <deser_dest>: A type name into which the queried value is deserialized
    • The specified type MUST implement the serde::Deserialize trait
    • If the type name contains only a single identifier, you can omit parentheses around it
  • <default_expr>: An expression for a default value in case of query failure
    • Instead, you can put default keyword in this place to use Default::default() as the default value

§Compatibility

query_value! can be used with arbitrary data structure(to call, Value) that supports get(&self, idx) -> Option<&Value> method that retrieves a value at idx.

Extracting mutable reference is also supported if your Value supports get_mut(&mut self, idx) -> Option<&Value>.

Instances of compatible data structures: