diff --git a/Cargo.toml b/Cargo.toml index 5cd9a53..29ed9d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,8 +3,14 @@ name = "json_dotpath" version = "0.1.0" authors = ["Ondřej Hruška "] edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +license = "MIT" +description = "Dotted path access to nested JSON objects (serde_json::Value)" +repository = "https://git.ondrovo.com/packages/json_dotpath" +readme = "README.md" +keywords = ["serde", "json", "dot"] +categories = [ + "data-structures" +] [dependencies] serde = "1" diff --git a/README.md b/README.md new file mode 100644 index 0000000..c6f8fac --- /dev/null +++ b/README.md @@ -0,0 +1,63 @@ +# json_dotpath + +Access members of nested JSON arrays and objects using "dotted paths". + +The `DotPaths` trait is implemented for `serde_json::Value`, +`serde_json::Map`, and `Vec`. + +Any serializable type or `serde_json::Value` can be stored to or retrieved from +the nested object. Any value stored in the object can also be modified in place +by getting a mutable reference. + +This crate is useful for tasks such as working with dynamic JSON API payloads, +parsing config files, or polymorphic data store. + +## Supported Operations + +### 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 + +### Array +Array is an ordered sequence backed by a Vec. It has these additional operations: + +- Prepend +- Append +- Insert, shifting the following elements +- Get the last element + +### 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. + +## Dotted Path Syntax + +Array keys must be numeric (integer), or one of the special patterns listed below. + +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 (`.`). + +### Array Index Patterns + +- `-` ... prepend +- `<` ... prepend (or get first) +- `+` ... append +- `>` ... append (or get last) +- `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. diff --git a/src/lib.rs b/src/lib.rs index ed2c9f1..57e605d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,8 @@ use serde_json::{Map, Value}; use std::cmp::Ordering; use std::mem; +#[macro_use] extern crate serde_derive; + /// Access and mutate nested JSON elements by dotted paths /// /// The path is composed of keys separated by dots, e.g. `foo.bar.1`. @@ -540,6 +542,7 @@ mod tests { use crate::DotPaths; use serde_json::json; use serde_json::Value; + use serde::{Serialize,Deserialize}; #[test] fn get_scalar_with_empty_path() { @@ -804,4 +807,49 @@ mod tests { ); assert_eq!(json!({"one": "two", "x": {"bbb": ["betelgeuze"]}}), vec); } + + #[test] + fn stamps() { + let mut stamps = Value::Null; + // null will become Value::Array(vec![]) + + #[derive(Serialize,Deserialize,PartialEq,Default)] + struct Stamp { + country: String, + year: u32, + color: String, + #[serde(rename = "face value")] + face_value: String, + }; + + stamps.dot_set("0", json!({ + "country": "British Mauritius", + "year": 1847, + "color": "orange", + "face value": "1 penny" + })); + + // append + stamps.dot_set(">", Stamp { + country: "British Mauritius".to_string(), + year: 1847, + color: "blue".to_string(), + face_value: "2 pence".to_string(), + }); + + assert_eq!("orange", stamps.dot_get::("0.color").unwrap()); + assert_eq!("blue", stamps.dot_get::("1.color").unwrap()); + + assert_eq!(1847, stamps.dot_get::("1").unwrap().year); + + // Remove the first stamp's "face value" attribute + assert_eq!(Some("1 penny".to_string()), stamps.dot_get("0.face value")); + stamps.dot_remove("0.face value"); + assert_eq!(Option::::None, stamps.dot_get("0.face value")); + + // change the second stamp's year + let old_year : u32 = stamps.dot_replace("1.year", 1850).unwrap(); + assert_eq!(1847, old_year); + assert_eq!(1850, stamps.dot_get::("1.year").unwrap()); + } }