|
|
|
@ -2,6 +2,23 @@ |
|
|
|
|
|
|
|
|
|
Access members of nested JSON arrays and objects using "dotted paths". |
|
|
|
|
|
|
|
|
|
## Changes |
|
|
|
|
|
|
|
|
|
### 1.0.0 |
|
|
|
|
|
|
|
|
|
The API changed to return `Result<Option<T>>` instead of panicking internally on error. |
|
|
|
|
The library is now much safer to use. |
|
|
|
|
|
|
|
|
|
Further, all logic has been adjusted to be more robust and consistent. |
|
|
|
|
|
|
|
|
|
Array append and prepend operators now use `<<` and `>>` instead of overloading `<` and `>`, |
|
|
|
|
which now work the same way in all array accesses (getting the first and last element). |
|
|
|
|
|
|
|
|
|
## Dotted path |
|
|
|
|
|
|
|
|
|
Dotted path represents a path from the root of a JSON object to one of its nodes. |
|
|
|
|
Such path is represented by joining the object and array keys by dots: |
|
|
|
|
|
|
|
|
|
Consider this example JSON: |
|
|
|
|
|
|
|
|
|
```json |
|
|
|
@ -14,90 +31,97 @@ Consider this example JSON: |
|
|
|
|
} |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
The following can be used to access its parts: |
|
|
|
|
- `obj.dot_get("fruit")` ... get the fruits array |
|
|
|
|
- `obj.dot_get("fruit.0.name")` ... 0th fruit name, "lemon" |
|
|
|
|
- `obj.dot_get("fruit.>.color")` ... last fruit's color, "red" |
|
|
|
|
The following paths represent its parts: |
|
|
|
|
|
|
|
|
|
The JSON can also be manipulated: |
|
|
|
|
- `""` ... the whole object |
|
|
|
|
- `"fruit"` ... the fruits array |
|
|
|
|
- `"fruit.0"` ... the first fruit object, `{"name": "lemon", "color": "yellow"}` |
|
|
|
|
- `"fruit.1.name"` ... the second (index is 0-based) fruit's name, `"apple"` |
|
|
|
|
|
|
|
|
|
- `obj.dot_take("fruit.1")` ... extract the "apple" object, removing it from the JSON |
|
|
|
|
- `obj.dot_set("fruit.<1", json!({"name":"plum","color":"blue"})` ... insert before the 1st element, shifting the rest |
|
|
|
|
- `obj.dot_set("fruit.>1", json!({"name":"plum","color":"blue"})` ... insert after the 1st element, shifting the rest |
|
|
|
|
- `obj.dot_set("fruit.>.name", "tangerine")` ... set the last fruit's name |
|
|
|
|
- `obj.dot_set("fruit.>", Value::Null)` ... append a JSON null |
|
|
|
|
- `obj.dot_set("fruit.<", true)` ... prepend a JSON true |
|
|
|
|
- `obj.dot_set("vegetables.onion.>", "aaa")` ... add `"vegetables": {"onion":["aaa"]}` to the outer object |
|
|
|
|
(the parent map and array are created automatically) |
|
|
|
|
Special patterns may be used for object manipulation as well (see below). |
|
|
|
|
|
|
|
|
|
Any serializable type or `serde_json::Value` can be stored to or retrieved from |
|
|
|
|
the nested object (`Value::Object`, `Value::Array`, `Value::Null`). |
|
|
|
|
|
|
|
|
|
Any value stored in the object can also be modified in place, without deserialization, |
|
|
|
|
by getting a mutable reference (`dot_get_mut(path)`). |
|
|
|
|
## Object operations |
|
|
|
|
|
|
|
|
|
This crate is useful for tasks such as working with dynamic JSON API payloads, |
|
|
|
|
parsing config files, or building a polymorphic data store. |
|
|
|
|
Five principal methods are added by the `DotPaths` trait to `serde_json::Value`, |
|
|
|
|
`Vec<Value>` and `serde_json::Map<String, Value>` (the inner structs of `Value::Object` and `Value::Array`). |
|
|
|
|
|
|
|
|
|
## Supported Operations |
|
|
|
|
- `dot_get(path)` - get a value by path |
|
|
|
|
- `dot_get_mut(path)` - get a mutable reference to an element of the JSON object |
|
|
|
|
- `dot_set(path, value)` - set a new value, dropping the original (if any) |
|
|
|
|
- `dot_replace(path, value)` - set a new value, returning the original (if any) |
|
|
|
|
- `dot_take(path, value)` - remove a value by path, returning it (if any) |
|
|
|
|
|
|
|
|
|
### Object and Array |
|
|
|
|
- Set (dropping the original value, if any) |
|
|
|
|
- Remove (remove and drop a value) |
|
|
|
|
- Take (remove a value and deserialize it) |
|
|
|
|
- Replace (take and set) |
|
|
|
|
- Get (find & deserialize) |
|
|
|
|
- Get a mutable reference to a Value |
|
|
|
|
`dot_set()` supports array manipulation syntax not found in the other methods, namely the |
|
|
|
|
`>n` and `<n` pattern to insert an element before or after an index, shifting the rest of the `Vec`. |
|
|
|
|
|
|
|
|
|
### Array |
|
|
|
|
Array is an ordered sequence backed by a Vec. It has these additional operations: |
|
|
|
|
### Additional convenience methods |
|
|
|
|
|
|
|
|
|
- Prepend |
|
|
|
|
- Append |
|
|
|
|
- Insert, shifting the following elements |
|
|
|
|
- Get the last element |
|
|
|
|
- `dot_remove(path)` - remove a value by path |
|
|
|
|
- `dot_get_or(path, def)` - get value, or a custom default |
|
|
|
|
- `dot_get_or_default(path)` - get value, or `Default::default()` |
|
|
|
|
|
|
|
|
|
### Null |
|
|
|
|
JSON null can become an array or object by setting it's members (even nested), as if it was an array or object. |
|
|
|
|
It becomes an array or object of the appropriate type based on the root key. |
|
|
|
|
All methods are generic and take care of serializing and deserializing the stored / retrieved |
|
|
|
|
data. `dot_get_mut()` is an exception and returns `&mut Value`. |
|
|
|
|
|
|
|
|
|
## Dotted Path Syntax |
|
|
|
|
`dot_set()` and `dot_remove()` do not deserialize the original object, which makes them more |
|
|
|
|
efficient than `dot_replace()` and `dot_take()` when the original value is not needed. |
|
|
|
|
|
|
|
|
|
### Map Patterns |
|
|
|
|
### Error reporting |
|
|
|
|
|
|
|
|
|
To avoid ambiguity, it's not allowed to use numeric keys (or keys starting with a number) |
|
|
|
|
as map keys. Map keys must start with an ASCII letter or underscore and must not contain a dot (`.`). |
|
|
|
|
All methods return `Result<json_dotpath::Error, T>` (aliased to `json_dotpath::Result<T>`), |
|
|
|
|
either as `json_dotpath::Result<()>`, or `json_dotpath::Result<Option<T>>` when a value is expected. |
|
|
|
|
|
|
|
|
|
Examples: |
|
|
|
|
These results should be handled carefully, as they report structural errors (meaning the requested operation |
|
|
|
|
could not be performed), or the path given was invalid. |
|
|
|
|
|
|
|
|
|
- `abc` |
|
|
|
|
- `_123` |
|
|
|
|
- `key with spaces` |
|
|
|
|
### Dynamic object building |
|
|
|
|
|
|
|
|
|
If a numeric key or a key nonconforming in other way must be used, prefix it with `#`. |
|
|
|
|
It will be taken literally as a string, excluding the prefix. |
|
|
|
|
When a path that does not exist but could (e.g. an appended array element, a new object key), and one of the assignment |
|
|
|
|
methods or `dot_get_mut()` are used, this element will be created automatically, including its parent elements as needed. |
|
|
|
|
|
|
|
|
|
e.g. to get 456 from `{"foo":{"123":456}}`, use `foo.#123` instead of `foo.123` |
|
|
|
|
This is well illustrated in one of the unit tests: |
|
|
|
|
|
|
|
|
|
```rust |
|
|
|
|
let mut obj = Value::Null; |
|
|
|
|
let _ = obj.dot_get_mut("foo.0").unwrap(); // get mut, here only for side effects |
|
|
|
|
assert_eq!(json!({"foo": [null]}), obj); |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
Null can flexibly become an array or object in such situations (see "Special handling of Null" below). |
|
|
|
|
|
|
|
|
|
## Dotted Path Syntax |
|
|
|
|
|
|
|
|
|
Path is simply a sequence of path segment joined by periods (`.`). |
|
|
|
|
|
|
|
|
|
Some symbols are ascribed special meaning by the library, depending on the method they're used in. |
|
|
|
|
All symbols (including `.`) may be escaped using a backslash if their literal value is needed as part of the path. |
|
|
|
|
|
|
|
|
|
### Array Patterns |
|
|
|
|
|
|
|
|
|
Array keys must be numeric (integer), or one of the special patterns listed below. |
|
|
|
|
|
|
|
|
|
- `-` ... prepend |
|
|
|
|
- `<` ... prepend (or get first) |
|
|
|
|
- `+` ... append |
|
|
|
|
- `>` ... append (or get last) |
|
|
|
|
- `<` ... first element |
|
|
|
|
- `>` ... last element |
|
|
|
|
- `-` or `<<` ... prepend |
|
|
|
|
- `+` or `>>` ... append |
|
|
|
|
- `<n`, e.g. `<5` ... insert before the n-th element |
|
|
|
|
- `>n`, e.g. `>5` ... insert after the n-th element |
|
|
|
|
|
|
|
|
|
### Path Examples |
|
|
|
|
|
|
|
|
|
- Empty path ... access the root element |
|
|
|
|
- `5` ... get the element `"five"` from `[0,1,2,3,4,"five"]` |
|
|
|
|
- `a.b.c` ... get `1` from `{ "a": { "b": { "c": 1 } } }` |
|
|
|
|
- `a.0.x` ... get `1` from `{ "a": [ { "x": 1 } ] }` |
|
|
|
|
|
|
|
|
|
It's possible to create nested arrays or objects by setting a non-existent path, |
|
|
|
|
provided the key syntax rules are maintained. |
|
|
|
|
|
|
|
|
|
See unit tests for more examples. |
|
|
|
|
|
|
|
|
|
### Special handling of Null |
|
|
|
|
|
|
|
|
|
JSON null in an object can transparently become an array or object by setting it's members (even nested), |
|
|
|
|
as if it was an empty array or object. Whether it should become an array or object depends on the key used to index into it. |
|
|
|
|
|
|
|
|
|
- numeric key turns null into an array (only `0` and the special array operators are allowed, |
|
|
|
|
as other numbers are out of range for an empty array) |
|
|
|
|
- any other key turns it into a map |
|
|
|
|
- any key starting with an escape creates a map as well (e.g. `\0.aaa` turns `null` into `{"0": {"aaa": …} }` ) |
|
|
|
|
|
|
|
|
|
JSON null is considered an empty value and is transformed into `Ok(None)` when retrieved, as it can not be deserialized. |
|
|
|
|
|
|
|
|
|
Setting a value to `Value::Null` works as expected and places a JSON null in the object. |
|
|
|
|