add #12346 for numeric map keys

master
Ondřej Hruška 4 years ago
parent 104f03101d
commit 7d8b4ce8f6
Signed by: MightyPork
GPG Key ID: 2C5FD5035250423D
  1. 2
      Cargo.toml
  2. 53
      README.md
  3. 42
      src/lib.rs

@ -1,6 +1,6 @@
[package] [package]
name = "json_dotpath" name = "json_dotpath"
version = "0.1.1" version = "0.1.2"
authors = ["Ondřej Hruška <ondra@ondrovo.com>"] authors = ["Ondřej Hruška <ondra@ondrovo.com>"]
edition = "2018" edition = "2018"
license = "MIT" license = "MIT"

@ -2,15 +2,41 @@
Access members of nested JSON arrays and objects using "dotted paths". Access members of nested JSON arrays and objects using "dotted paths".
The `DotPaths` trait is implemented for `serde_json::Value`, Consider this example JSON:
`serde_json::Map<String, serde_json::Value>`, and `Vec<serde_json::Value>`.
```json
{
"fruit": [
{"name": "lemon", "color": "yellow"},
{"name": "apple", "color": "green"},
{"name": "cherry", "color": "red"}
]
}
```
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 JSON can also be manipulated:
- `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 object
Any serializable type or `serde_json::Value` can be stored to or retrieved from 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 the nested object (`Value::Object`, `Value::Array`, `Value::Null`).
by getting a mutable reference.
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, This crate is useful for tasks such as working with dynamic JSON API payloads,
parsing config files, or polymorphic data store. parsing config files, or building a polymorphic data store.
## Supported Operations ## Supported Operations
@ -36,12 +62,25 @@ It becomes an array or object of the appropriate type based on the root key.
## Dotted Path Syntax ## Dotted Path Syntax
Array keys must be numeric (integer), or one of the special patterns listed below. ### Map Patterns
To avoid ambiguity, it's not allowed to use numeric keys (or keys starting with a number) 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 (`.`). as map keys. Map keys must start with an ASCII letter or underscore and must not contain a dot (`.`).
### Array Index Patterns Examples:
- `abc`
- `_123`
- `key with spaces`
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.
e.g. to get 456 from `{"foo":{"123":456}}`, use `foo.#123` instead of `foo.123`
### Array Patterns
Array keys must be numeric (integer), or one of the special patterns listed below.
- `-` ... prepend - `-` ... prepend
- `<` ... prepend (or get first) - `<` ... prepend (or get first)

@ -23,6 +23,8 @@ pub trait DotPaths {
/// # Special keys /// # Special keys
/// Arrays can be indexed by special keys for reading: /// Arrays can be indexed by special keys for reading:
/// - `>` ... last element /// - `>` ... last element
/// - `#123` ... map keys may be prefixed by `#` to use numeric strings
/// or other unusual forms (excluding dot `.`, which is still illegal in keys)
/// ///
/// # Panics /// # Panics
/// - If the path attempts to index into a scalar (e.g. `"foo.bar"` in `{"foo": 123}`) /// - If the path attempts to index into a scalar (e.g. `"foo.bar"` in `{"foo": 123}`)
@ -252,14 +254,23 @@ where
/// Check if a key is valid to use by dot paths in Value::Object. /// Check if a key is valid to use by dot paths in Value::Object.
/// The key must start with an alpha character or underscore and must not contain period. /// The key must start with an alpha character or underscore and must not contain period.
fn validate_map_key(key: &str) { #[must_use]
if key.parse::<usize>().is_ok() { fn validate_map_key(key: &str) -> &str {
panic!("Numeric keys are not allowed in maps: {}", key); if key.contains('.') {
// this shouldn't happen due to the way the splitting works
panic!("Invalid map key: {}", key);
}
// 'literal modifier', e.g. for numeric map keys
if key.starts_with('#') {
return &key[1..];
} }
if !key.starts_with(|p: char| p.is_ascii_alphabetic() || p == '_') || key.contains('.') { if !key.starts_with(|p: char| p.is_ascii_alphabetic() || p == '_') {
panic!("Invalid map key: {}", key); panic!("Invalid map key: {}", key);
} }
key
} }
impl DotPaths for serde_json::Map<String, serde_json::Value> { impl DotPaths for serde_json::Map<String, serde_json::Value> {
@ -268,7 +279,7 @@ impl DotPaths for serde_json::Map<String, serde_json::Value> {
T: DeserializeOwned, T: DeserializeOwned,
{ {
let (my, sub) = path_split(path); let (my, sub) = path_split(path);
validate_map_key(my); let my = validate_map_key(my);
if let Some(sub_path) = sub { if let Some(sub_path) = sub {
self.get(my) self.get(my)
@ -285,7 +296,7 @@ impl DotPaths for serde_json::Map<String, serde_json::Value> {
fn dot_get_mut(&mut self, path: &str) -> Option<&mut Value> { fn dot_get_mut(&mut self, path: &str) -> Option<&mut Value> {
let (my, sub) = path_split(path); let (my, sub) = path_split(path);
validate_map_key(my); let my = validate_map_key(my);
if let Some(sub_path) = sub { if let Some(sub_path) = sub {
self.get_mut(my) self.get_mut(my)
@ -301,7 +312,7 @@ impl DotPaths for serde_json::Map<String, serde_json::Value> {
T: Serialize, T: Serialize,
{ {
let (my, sub) = path_split(path); let (my, sub) = path_split(path);
validate_map_key(my); let my = validate_map_key(my);
if let Some(subpath) = sub { if let Some(subpath) = sub {
if self.contains_key(my) { if self.contains_key(my) {
@ -321,7 +332,7 @@ impl DotPaths for serde_json::Map<String, serde_json::Value> {
U: DeserializeOwned, U: DeserializeOwned,
{ {
let (my, sub) = path_split(path); let (my, sub) = path_split(path);
validate_map_key(my); let my = validate_map_key(my);
if let Some(subpath) = sub { if let Some(subpath) = sub {
if self.contains_key(my) { if self.contains_key(my) {
@ -344,7 +355,7 @@ impl DotPaths for serde_json::Map<String, serde_json::Value> {
T: DeserializeOwned, T: DeserializeOwned,
{ {
let (my, sub) = path_split(path); let (my, sub) = path_split(path);
validate_map_key(my); let my = validate_map_key(my);
if let Some(subpath) = sub { if let Some(subpath) = sub {
if let Some(item) = self.get_mut(my) { if let Some(item) = self.get_mut(my) {
@ -770,6 +781,19 @@ mod tests {
assert_eq!(None, vec.dot_get::<Value>("xxx")); assert_eq!(None, vec.dot_get::<Value>("xxx"));
} }
#[test]
fn map_escaped_keys() {
let mut map = json!({});
map.dot_set("#0.#1", 123);
assert_eq!(json!({"0": {"1": 123}}), map);
map.dot_set("#0.#2", 456);
assert_eq!(Some(123), map.dot_get("#0.#1"));
assert_eq!(Some(123), map.dot_take("#0.#1"));
assert_eq!(json!({"0": {"2": 456}}), map);
assert_eq!(true, map.dot_remove("#0.#2"));
}
#[test] #[test]
fn remove_from_map() { fn remove_from_map() {
let mut vec = json!({"one": "two", "x": "y"}); let mut vec = json!({"one": "two", "x": "y"});

Loading…
Cancel
Save