v1 many fixes, new api

master
Ondřej Hruška 4 years ago
parent c6b113479e
commit f5059c8510
Signed by: MightyPork
GPG Key ID: 2C5FD5035250423D
  1. 4
      Cargo.toml
  2. 142
      README.md
  3. 1048
      src/lib.rs

@ -1,6 +1,6 @@
[package] [package]
name = "json_dotpath" name = "json_dotpath"
version = "0.1.2" version = "1.0.0"
authors = ["Ondřej Hruška <ondra@ondrovo.com>"] authors = ["Ondřej Hruška <ondra@ondrovo.com>"]
edition = "2018" edition = "2018"
license = "MIT" license = "MIT"
@ -16,3 +16,5 @@ categories = [
serde = "1" serde = "1"
serde_derive = "1" serde_derive = "1"
serde_json = "1" serde_json = "1"
failure = "0.1.7"
failure_derive = "0.1.7"

@ -2,6 +2,23 @@
Access members of nested JSON arrays and objects using "dotted paths". 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: Consider this example JSON:
```json ```json
@ -14,90 +31,97 @@ Consider this example JSON:
} }
``` ```
The following can be used to access its parts: The following paths represent 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 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 Special patterns may be used for object manipulation as well (see below).
- `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)
Any serializable type or `serde_json::Value` can be stored to or retrieved from ## Object operations
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)`).
This crate is useful for tasks such as working with dynamic JSON API payloads, Five principal methods are added by the `DotPaths` trait to `serde_json::Value`,
parsing config files, or building a polymorphic data store. `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 `dot_set()` supports array manipulation syntax not found in the other methods, namely the
- Set (dropping the original value, if any) `>n` and `<n` pattern to insert an element before or after an index, shifting the rest of the `Vec`.
- 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
### Array ### Additional convenience methods
Array is an ordered sequence backed by a Vec. It has these additional operations:
- Prepend - `dot_remove(path)` - remove a value by path
- Append - `dot_get_or(path, def)` - get value, or a custom default
- Insert, shifting the following elements - `dot_get_or_default(path)` - get value, or `Default::default()`
- Get the last element
### Null All methods are generic and take care of serializing and deserializing the stored / retrieved
JSON null can become an array or object by setting it's members (even nested), as if it was an array or object. data. `dot_get_mut()` is an exception and returns `&mut Value`.
It becomes an array or object of the appropriate type based on the root key.
## 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) All methods return `Result<json_dotpath::Error, T>` (aliased to `json_dotpath::Result<T>`),
as map keys. Map keys must start with an ASCII letter or underscore and must not contain a dot (`.`). 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` ### Dynamic object building
- `_123`
- `key with spaces`
If a numeric key or a key nonconforming in other way must be used, prefix it with `#`. When a path that does not exist but could (e.g. an appended array element, a new object key), and one of the assignment
It will be taken literally as a string, excluding the prefix. 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 Patterns
Array keys must be numeric (integer), or one of the special patterns listed below. Array keys must be numeric (integer), or one of the special patterns listed below.
- `-` ... prepend - `<` ... first element
- `<` ... prepend (or get first) - `>` ... last element
- `+` ... append - `-` or `<<` ... prepend
- `>` ... append (or get last) - `+` or `>>` ... append
- `<n`, e.g. `<5` ... insert before the n-th element - `<n`, e.g. `<5` ... insert before the n-th element
- `>n`, e.g. `>5` ... insert after 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, It's possible to create nested arrays or objects by setting a non-existent path,
provided the key syntax rules are maintained. provided the key syntax rules are maintained.
See unit tests for more examples. 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.

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save