add #12346 for numeric map keys
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "json_dotpath"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
authors = ["Ondřej Hruška <ondra@ondrovo.com>"]
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
|
||||
@@ -2,15 +2,41 @@
|
||||
|
||||
Access members of nested JSON arrays and objects using "dotted paths".
|
||||
|
||||
The `DotPaths` trait is implemented for `serde_json::Value`,
|
||||
`serde_json::Map<String, serde_json::Value>`, and `Vec<serde_json::Value>`.
|
||||
Consider this example JSON:
|
||||
|
||||
```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
|
||||
the nested object. Any value stored in the object can also be modified in place
|
||||
by getting a mutable reference.
|
||||
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,
|
||||
parsing config files, or polymorphic data store.
|
||||
parsing config files, or building a polymorphic data store.
|
||||
|
||||
## Supported Operations
|
||||
|
||||
@@ -36,12 +62,25 @@ 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.
|
||||
### Map Patterns
|
||||
|
||||
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
|
||||
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 (or get first)
|
||||
|
||||
+35
-11
@@ -23,6 +23,8 @@ pub trait DotPaths {
|
||||
/// # Special keys
|
||||
/// Arrays can be indexed by special keys for reading:
|
||||
/// - `>` ... 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
|
||||
/// - 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.
|
||||
/// The key must start with an alpha character or underscore and must not contain period.
|
||||
fn validate_map_key(key: &str) {
|
||||
if key.parse::<usize>().is_ok() {
|
||||
panic!("Numeric keys are not allowed in maps: {}", key);
|
||||
}
|
||||
|
||||
if !key.starts_with(|p: char| p.is_ascii_alphabetic() || p == '_') || key.contains('.') {
|
||||
#[must_use]
|
||||
fn validate_map_key(key: &str) -> &str {
|
||||
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 == '_') {
|
||||
panic!("Invalid map key: {}", key);
|
||||
}
|
||||
|
||||
key
|
||||
}
|
||||
|
||||
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,
|
||||
{
|
||||
let (my, sub) = path_split(path);
|
||||
validate_map_key(my);
|
||||
let my = validate_map_key(my);
|
||||
|
||||
if let Some(sub_path) = sub {
|
||||
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> {
|
||||
let (my, sub) = path_split(path);
|
||||
validate_map_key(my);
|
||||
let my = validate_map_key(my);
|
||||
|
||||
if let Some(sub_path) = sub {
|
||||
self.get_mut(my)
|
||||
@@ -301,7 +312,7 @@ impl DotPaths for serde_json::Map<String, serde_json::Value> {
|
||||
T: Serialize,
|
||||
{
|
||||
let (my, sub) = path_split(path);
|
||||
validate_map_key(my);
|
||||
let my = validate_map_key(my);
|
||||
|
||||
if let Some(subpath) = sub {
|
||||
if self.contains_key(my) {
|
||||
@@ -321,7 +332,7 @@ impl DotPaths for serde_json::Map<String, serde_json::Value> {
|
||||
U: DeserializeOwned,
|
||||
{
|
||||
let (my, sub) = path_split(path);
|
||||
validate_map_key(my);
|
||||
let my = validate_map_key(my);
|
||||
|
||||
if let Some(subpath) = sub {
|
||||
if self.contains_key(my) {
|
||||
@@ -344,7 +355,7 @@ impl DotPaths for serde_json::Map<String, serde_json::Value> {
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
let (my, sub) = path_split(path);
|
||||
validate_map_key(my);
|
||||
let my = validate_map_key(my);
|
||||
|
||||
if let Some(subpath) = sub {
|
||||
if let Some(item) = self.get_mut(my) {
|
||||
@@ -770,6 +781,19 @@ mod tests {
|
||||
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]
|
||||
fn remove_from_map() {
|
||||
let mut vec = json!({"one": "two", "x": "y"});
|
||||
|
||||
Reference in New Issue
Block a user