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]
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)

@ -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);
#[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 == '_') || key.contains('.') {
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"});

Loading…
Cancel
Save