add dot_has
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "json_dotpath"
|
||||
version = "1.0.3"
|
||||
version = "1.1.0"
|
||||
authors = ["Ondřej Hruška <ondra@ondrovo.com>"]
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
|
||||
@@ -4,6 +4,10 @@ Access members of nested JSON arrays and objects using "dotted paths".
|
||||
|
||||
## Changes
|
||||
|
||||
### 1.1.0
|
||||
|
||||
Added `dot_has()` and `dot_has_checked()`
|
||||
|
||||
### 1.0.3
|
||||
|
||||
Replaced `failure` with `thiserror`, implement `std::error::Error` for the error type.
|
||||
@@ -63,6 +67,8 @@ Five principal methods are added by the `DotPaths` trait to `serde_json::Value`,
|
||||
- `dot_remove(path)` - remove a value by path
|
||||
- `dot_get_or(path, def)` - get value, or a custom default
|
||||
- `dot_get_or_default(path)` - get value, or `Default::default()`
|
||||
- `dot_has_checked(path)` - checks if a path is valid and a value exists there
|
||||
- `dot_has(path)` - the same as above, but errors silently become `false`
|
||||
|
||||
All methods are generic and take care of serializing and deserializing the stored / retrieved
|
||||
data. `dot_get_mut()` is an exception and returns `&mut Value`.
|
||||
@@ -118,7 +124,7 @@ See unit tests for more examples.
|
||||
|
||||
### Special handling of Null
|
||||
|
||||
JSON null in an object can transparently become an array or object by setting it's members (even nested),
|
||||
JSON null can transparently become an array or object by setting it's members (even nested),
|
||||
as if it was an empty array or object. Whether it should become an array or object depends on the key used to index into it.
|
||||
|
||||
- numeric key turns null into an array (only `0` and the special array operators are allowed,
|
||||
@@ -126,6 +132,8 @@ as if it was an empty array or object. Whether it should become an array or obje
|
||||
- any other key turns it into a map
|
||||
- any key starting with an escape creates a map as well (e.g. `\0.aaa` turns `null` into `{"0": {"aaa": …} }` )
|
||||
|
||||
JSON null is considered an empty value and is transformed into `Ok(None)` when retrieved, as it can not be deserialized.
|
||||
JSON null is considered an empty value and is transformed into `Ok(None)` when retrieved,
|
||||
as it can not be deserialized.
|
||||
|
||||
Setting a value to `Value::Null` works as expected and places a JSON null in the object.
|
||||
Setting a value to `Value::Null` works as expected and places a JSON null in the object, the same
|
||||
applies when getting a mutable reference.
|
||||
|
||||
+120
-2
@@ -112,6 +112,27 @@ pub trait DotPaths {
|
||||
self.dot_get_or(path, T::default())
|
||||
}
|
||||
|
||||
/// Check if a value exists under a dotted path.
|
||||
/// Returns error if the path is invalid, a string key was used to index into an array.
|
||||
///
|
||||
/// # Special symbols
|
||||
/// - `>` ... last element of an array
|
||||
/// - `<` ... first element of an array (same as `0`)
|
||||
fn dot_has_checked(&self, path: &str) -> Result<bool>;
|
||||
|
||||
/// Check if a value exists under a dotted path.
|
||||
/// Returns false also when the path is invalid.
|
||||
///
|
||||
/// Use `dot_has_checked` if you want to distinguish non-existent values from path errors.
|
||||
///
|
||||
/// # Special symbols
|
||||
/// - `>` ... last element of an array
|
||||
/// - `<` ... first element of an array (same as `0`)
|
||||
fn dot_has(&self, path: &str) -> bool {
|
||||
self.dot_has_checked(path)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Get a mutable reference to an item
|
||||
///
|
||||
/// If the path does not exist but a value on the path can be created (i.e. because the path
|
||||
@@ -229,6 +250,22 @@ impl DotPaths for serde_json::Value {
|
||||
}
|
||||
}
|
||||
|
||||
fn dot_has_checked(&self, path: &str) -> Result<bool> {
|
||||
match self {
|
||||
Value::Array(vec) => vec.dot_has_checked(path),
|
||||
Value::Object(map) => map.dot_has_checked(path),
|
||||
Value::Null => Ok(false),
|
||||
_ => {
|
||||
if path.is_empty() {
|
||||
Ok(true)
|
||||
} else {
|
||||
// Path continues, but we can't traverse into a scalar
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dot_get_mut(&mut self, path: &str) -> Result<&mut Value> {
|
||||
match self {
|
||||
Value::Array(vec) => vec.dot_get_mut(path),
|
||||
@@ -261,7 +298,7 @@ impl DotPaths for serde_json::Value {
|
||||
Value::Object(map) => map.dot_replace(path, value),
|
||||
Value::Null => {
|
||||
// spawn new
|
||||
mem::replace(self, new_by_path_root(path, value)?);
|
||||
*self = new_by_path_root(path, value)?;
|
||||
Ok(None)
|
||||
}
|
||||
_ => {
|
||||
@@ -366,6 +403,26 @@ impl DotPaths for serde_json::Map<String, serde_json::Value> {
|
||||
}
|
||||
}
|
||||
|
||||
fn dot_has_checked(&self, path: &str) -> Result<bool> {
|
||||
let (my, sub) = path_split(path);
|
||||
|
||||
if my.is_empty() {
|
||||
return Err(InvalidKey(my));
|
||||
}
|
||||
|
||||
if let Some(sub_path) = sub {
|
||||
match self.get(&my).null_to_none() {
|
||||
None => Ok(false),
|
||||
Some(child) => child.dot_has_checked(sub_path),
|
||||
}
|
||||
} else {
|
||||
match self.get(&my).null_to_none() {
|
||||
None => Ok(false),
|
||||
Some(_) => Ok(true),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::collapsible_if)]
|
||||
fn dot_get_mut(&mut self, path: &str) -> Result<&mut Value> {
|
||||
let (my, sub) = path_split(path);
|
||||
@@ -516,6 +573,43 @@ impl DotPaths for Vec<serde_json::Value> {
|
||||
}
|
||||
}
|
||||
|
||||
fn dot_has_checked(&self, path: &str) -> Result<bool> {
|
||||
let (my, sub) = path_split(path);
|
||||
|
||||
if my.is_empty() {
|
||||
return Err(InvalidKey(my));
|
||||
}
|
||||
|
||||
if self.is_empty() {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let index: usize = match my.as_str() {
|
||||
">" => self.len() - 1, // non-empty checked above
|
||||
"<" => 0,
|
||||
_ => my.parse().map_err(|_| {
|
||||
InvalidKey(my)
|
||||
})?,
|
||||
};
|
||||
|
||||
if index >= self.len() {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
if let Some(subpath) = sub {
|
||||
match self.get(index).null_to_none() {
|
||||
None => Ok(false),
|
||||
Some(child) => child.dot_has_checked(subpath),
|
||||
}
|
||||
} else {
|
||||
match self.get(index).null_to_none() {
|
||||
// null is reported as unset
|
||||
None => Ok(false),
|
||||
Some(_) => Ok(true),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::collapsible_if)]
|
||||
fn dot_get_mut(&mut self, path: &str) -> Result<&mut Value> {
|
||||
let (my, sub) = path_split(path);
|
||||
@@ -1101,7 +1195,7 @@ mod tests {
|
||||
// Borrow Null as mutable
|
||||
let mut obj = Value::Null;
|
||||
let m = obj.dot_get_mut("").unwrap();
|
||||
std::mem::replace(m, Value::from(123));
|
||||
*m = Value::from(123);
|
||||
assert_eq!(Value::from(123), obj);
|
||||
|
||||
// Create a parents path
|
||||
@@ -1143,6 +1237,30 @@ mod tests {
|
||||
assert_eq!(json!([{"foo": {"bar": {"dog": "cat"}}}]), Value::Array(obj));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn has() {
|
||||
let value = json!({
|
||||
"one": "two",
|
||||
"x": [1, 2, {"foo": 123}]
|
||||
});
|
||||
assert!(value.dot_has("one"));
|
||||
assert!(!value.dot_has("two"));
|
||||
assert!(value.dot_has("x"));
|
||||
assert!(value.dot_has("x.0"));
|
||||
assert!(value.dot_has("x.<"));
|
||||
assert!(value.dot_has("x.>"));
|
||||
assert!(value.dot_has("x.>.foo"));
|
||||
assert!(!value.dot_has("x.banana"));
|
||||
assert!(!value.dot_has("x.>.foo.bar"));
|
||||
assert!(value.dot_has_checked("x.banana").is_err());
|
||||
|
||||
if let Ok(false) = value.dot_has_checked("x.9999") {
|
||||
//
|
||||
} else {
|
||||
panic!("dot_has_checked failed");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stamps() {
|
||||
let mut stamps = Value::Null;
|
||||
|
||||
Reference in New Issue
Block a user