3 Commits
3 changed files with 104 additions and 37 deletions
+2 -3
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "json_dotpath" name = "json_dotpath"
version = "1.0.0" version = "1.0.3"
authors = ["Ondřej Hruška <ondra@ondrovo.com>"] authors = ["Ondřej Hruška <ondra@ondrovo.com>"]
edition = "2018" edition = "2018"
license = "MIT" license = "MIT"
@@ -16,5 +16,4 @@ categories = [
serde = "1" serde = "1"
serde_derive = "1" serde_derive = "1"
serde_json = "1" serde_json = "1"
failure = "0.1.7" thiserror = "1"
failure_derive = "0.1.7"
+5 -1
View File
@@ -4,6 +4,10 @@ Access members of nested JSON arrays and objects using "dotted paths".
## Changes ## Changes
### 1.0.3
Replaced `failure` with `thiserror`, implement `std::error::Error` for the error type.
### 1.0.0 ### 1.0.0
The API changed to return `Result<Option<T>>` instead of panicking internally on error. The API changed to return `Result<Option<T>>` instead of panicking internally on error.
@@ -49,7 +53,7 @@ Five principal methods are added by the `DotPaths` trait to `serde_json::Value`,
- `dot_get_mut(path)` - get a mutable reference to an element of the JSON object - `dot_get_mut(path)` - get a mutable reference to an element of the JSON object
- `dot_set(path, value)` - set a new value, dropping the original (if any) - `dot_set(path, value)` - set a new value, dropping the original (if any)
- `dot_replace(path, value)` - set a new value, returning the original (if any) - `dot_replace(path, value)` - set a new value, returning the original (if any)
- `dot_take(path, value)` - remove a value by path, returning it (if any) - `dot_take(path)` - remove a value by path, returning it (if any)
`dot_set()` supports array manipulation syntax not found in the other methods, namely the `dot_set()` supports array manipulation syntax not found in the other methods, namely the
`>n` and `<n` pattern to insert an element before or after an index, shifting the rest of the `Vec`. `>n` and `<n` pattern to insert an element before or after an index, shifting the rest of the `Vec`.
+97 -33
View File
@@ -2,41 +2,34 @@ use serde::de::DeserializeOwned;
use serde::Serialize; use serde::Serialize;
use serde_json::{Map, Value}; use serde_json::{Map, Value};
use std::mem; use std::mem;
use thiserror::Error;
#[cfg(test)] #[cfg(test)]
#[macro_use] #[macro_use]
extern crate serde_derive; extern crate serde_derive;
#[macro_use]
extern crate failure;
/// Errors from dot_path methods /// Errors from dot_path methods
#[derive(Debug, Fail)] #[derive(Debug, Error)]
pub enum Error { pub enum Error {
/// Path hit a value in the JSON object that is not array or map /// Path hit a value in the JSON object that is not array or map
/// and could not continue the traversal. /// and could not continue the traversal.
/// ///
/// (e.g. `foo.bar` in `{"foo": 123}`) /// (e.g. `foo.bar` in `{"foo": 123}`)
#[fail(display = "Unexpected value reached while traversing path")] #[error("Unexpected value reached while traversing path")]
BadPathElement, BadPathElement,
/// Array index out of range /// Array index out of range
#[fail(display = "Invalid array index: {}", _0)] #[error("Invalid array index: {0}")]
BadIndex(usize), BadIndex(usize),
/// Invalid (usually empty) key used in Map or Array. /// Invalid (usually empty) key used in Map or Array.
/// If the key is valid but out of bounds, `BadIndex` will be used. /// If the key is valid but out of bounds, `BadIndex` will be used.
#[fail(display = "Invalid key: {}", _0)] #[error("Invalid key: {0}")]
InvalidKey(String), InvalidKey(String),
/// Error serializing or deserializing a value /// Error serializing or deserializing a value
#[fail(display = "Invalid array or map key")] #[error("Invalid array or map key")]
SerdeError(#[fail(cause)] serde_json::Error), SerdeError(#[from] serde_json::Error),
}
impl From<serde_json::Error> for Error {
fn from(e: serde_json::Error) -> Self {
Error::SerdeError(e)
}
} }
use crate::Error::{BadIndex, BadPathElement, InvalidKey}; use crate::Error::{BadIndex, BadPathElement, InvalidKey};
@@ -145,14 +138,7 @@ pub trait DotPaths {
/// - `<` ... first element of an array (same as `0`) /// - `<` ... first element of an array (same as `0`)
fn dot_set<T>(&mut self, path: &str, value: T) -> Result<()> fn dot_set<T>(&mut self, path: &str, value: T) -> Result<()>
where where
T: Serialize, T: Serialize;
{
// This is a default implementation.
// Vec uses a custom implementation to support the special syntax.
let _ = self.dot_replace::<T, Value>(path, value)?; // Original value is dropped
Ok(())
}
/// Replace a value by path with a new value. /// Replace a value by path with a new value.
/// The value types do not have to match. /// The value types do not have to match.
@@ -321,6 +307,7 @@ impl DotPaths for serde_json::Value {
match self { match self {
// Special case for Vec, which implements additional path symbols // Special case for Vec, which implements additional path symbols
Value::Array(a) => a.dot_set(path, value), Value::Array(a) => a.dot_set(path, value),
Value::Object(m) => m.dot_set(path, value),
_ => { _ => {
let _ = self.dot_replace::<T, Value>(path, value)?; // Original value is dropped let _ = self.dot_replace::<T, Value>(path, value)?; // Original value is dropped
Ok(()) Ok(())
@@ -412,6 +399,29 @@ impl DotPaths for serde_json::Map<String, serde_json::Value> {
} }
} }
fn dot_set<T>(&mut self, path: &str, value: T) -> Result<()> where
T: Serialize {
let (my, sub) = path_split(path);
if my.is_empty() {
return Err(InvalidKey(my));
}
if let Some(subpath) = sub {
if self.contains_key(&my) {
self.get_mut(&my).unwrap().dot_set(subpath, value)
} else {
// Build new subpath
let _ = self.insert(my, new_by_path_root(subpath, value)?); // always returns None here
Ok(())
}
} else {
let packed = serde_json::to_value(value)?;
self.insert(my, packed);
Ok(())
}
}
fn dot_replace<NEW, OLD>(&mut self, path: &str, value: NEW) -> Result<Option<OLD>> fn dot_replace<NEW, OLD>(&mut self, path: &str, value: NEW) -> Result<Option<OLD>>
where where
NEW: Serialize, NEW: Serialize,
@@ -425,10 +435,7 @@ impl DotPaths for serde_json::Map<String, serde_json::Value> {
if let Some(subpath) = sub { if let Some(subpath) = sub {
if self.contains_key(&my) { if self.contains_key(&my) {
match self.get_mut(&my) { self.get_mut(&my).unwrap().dot_replace(subpath, value)
None => Ok(None),
Some(m) => m.dot_replace(subpath, value),
}
} else { } else {
// Build new subpath // Build new subpath
let _ = self.insert(my, new_by_path_root(subpath, value)?); // always returns None here let _ = self.insert(my, new_by_path_root(subpath, value)?); // always returns None here
@@ -487,7 +494,9 @@ impl DotPaths for Vec<serde_json::Value> {
let index: usize = match my.as_str() { let index: usize = match my.as_str() {
">" => self.len() - 1, // non-empty checked above ">" => self.len() - 1, // non-empty checked above
"<" => 0, "<" => 0,
_ => my.parse().map_err(|_| InvalidKey(my))?, _ => my.parse().map_err(|_| {
InvalidKey(my)
})?,
}; };
if index >= self.len() { if index >= self.len() {
@@ -524,7 +533,9 @@ impl DotPaths for Vec<serde_json::Value> {
} }
} }
"<" => 0, "<" => 0,
_ => my.parse().map_err(|_| InvalidKey(my))?, _ => my.parse().map_err(|_| {
InvalidKey(my)
})?,
}; };
if index > self.len() { if index > self.len() {
@@ -593,14 +604,23 @@ impl DotPaths for Vec<serde_json::Value> {
_ if my.starts_with('>') => { _ if my.starts_with('>') => {
// insert after // insert after
insert = true; insert = true;
(&my[1..]).parse::<usize>().map_err(|_| InvalidKey(my_s))? + 1 (&my[1..]).parse::<usize>()
.map_err(|_| {
InvalidKey(my_s)
})? + 1
} }
_ if my.starts_with('<') => { _ if my.starts_with('<') => {
// insert before // insert before
insert = true; insert = true;
(&my[1..]).parse::<usize>().map_err(|_| InvalidKey(my_s))? (&my[1..]).parse::<usize>()
.map_err(|_| {
InvalidKey(my_s)
})?
} }
_ => my.parse::<usize>().map_err(|_| InvalidKey(my_s))?, _ => my.parse::<usize>()
.map_err(|_| {
InvalidKey(my_s)
})?,
}; };
if index > self.len() { if index > self.len() {
@@ -657,7 +677,9 @@ impl DotPaths for Vec<serde_json::Value> {
} }
} }
"<" => 0, "<" => 0,
_ => my.parse().map_err(|_| InvalidKey(my))?, _ => my.parse().map_err(|_| {
InvalidKey(my)
})?,
}; };
if index >= self.len() { if index >= self.len() {
@@ -700,7 +722,9 @@ impl DotPaths for Vec<serde_json::Value> {
} }
} }
"<" => 0, "<" => 0,
_ => my.parse().map_err(|_| InvalidKey(my))?, _ => my.parse().map_err(|_| {
InvalidKey(my)
})?,
}; };
if index >= self.len() { if index >= self.len() {
@@ -838,6 +862,46 @@ mod tests {
assert_eq!(json!([[["first"]], [["second"]]]), vec); assert_eq!(json!([[["first"]], [["second"]]]), vec);
} }
#[test]
fn array_append1() {
let mut test_array = Value::Array(vec![]);
test_array.dot_set(">>", Value::String(String::from("Go to class"))).unwrap();
test_array.dot_set(">>", Value::String(String::from("Fish"))).unwrap();
assert_eq!(json!(["Go to class","Fish"]), test_array);
}
#[test]
fn array_append2() {
let mut test_array = Value::Array(vec![]);
test_array.dot_set("+", Value::String(String::from("Go to class"))).unwrap();
test_array.dot_set("+", Value::String(String::from("Fish"))).unwrap();
assert_eq!(json!(["Go to class","Fish"]), test_array);
}
#[test]
fn array_append_in_object1() {
let mut test_inner_array = Value::Null;
test_inner_array.dot_set("todos.+", Value::String(String::from("Go to class"))).unwrap();
assert_eq!(json!({"todos" : ["Go to class"] }), test_inner_array);
test_inner_array.dot_set("todos.+", Value::String(String::from("Fish"))).unwrap();
assert_eq!(json!({"todos" : ["Go to class","Fish"] }), test_inner_array);
}
#[test]
fn array_append_in_object2() {
let mut test_inner_array = Value::Null;
test_inner_array.dot_set("name", Value::String(String::from("Google"))).unwrap();
assert_eq!(json!({"name" : "Google"}), test_inner_array);
test_inner_array.dot_set("todos.+", Value::String(String::from("Go to class"))).unwrap();
assert_eq!(json!({"name" : "Google", "todos" : ["Go to class"] }), test_inner_array);
test_inner_array.dot_set("todos.+", Value::String(String::from("Fish"))).unwrap();
assert_eq!(json!({"name" : "Google", "todos" : ["Go to class","Fish"] }), test_inner_array);
}
#[test] #[test]
fn get_vec() { fn get_vec() {
let vec = json!([ let vec = json!([