add #12346 for numeric map keys
This commit is contained in:
+1
-1
@@ -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)
|
||||||
|
|||||||
+35
-11
@@ -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
|
||||||
|
|
||||||
if !key.starts_with(|p: char| p.is_ascii_alphabetic() || p == '_') || key.contains('.') {
|
|
||||||
panic!("Invalid map key: {}", key);
|
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 == '_') {
|
||||||
|
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"});
|
||||||
|
|||||||
Reference in New Issue
Block a user