Skip to content

Commit 391145b

Browse files
authored
Merge pull request #45 from akoshchiy/11270-json-delete-by-path
feat: add delete_by_keypath
2 parents e21b273 + 85c1e2e commit 391145b

File tree

2 files changed

+262
-8
lines changed

2 files changed

+262
-8
lines changed

src/functions.rs

Lines changed: 209 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use core::convert::TryInto;
1616
use std::borrow::Cow;
1717
use std::cmp::Ordering;
18+
use std::collections::BTreeMap;
1819
use std::collections::VecDeque;
1920
use std::str::from_utf8;
2021
use std::str::from_utf8_unchecked;
@@ -288,7 +289,7 @@ pub fn get_by_name(value: &[u8], name: &str, ignore_case: bool) -> Option<Vec<u8
288289
}
289290

290291
/// Extracts JSON sub-object at the specified path,
291-
/// where path elements can be either field keys or array indexes encoded in utf-8 string.
292+
/// where path elements can be either field keys or array indexes.
292293
pub fn get_by_keypath<'a, I: Iterator<Item = &'a KeyPath<'a>>>(
293294
value: &[u8],
294295
keypaths: I,
@@ -2015,6 +2016,211 @@ fn concat_jsonb(left: &[u8], right: &[u8], buf: &mut Vec<u8>) -> Result<(), Erro
20152016
Ok(())
20162017
}
20172018

2019+
/// Deletes a value from a JSON object by the specified path,
2020+
/// where path elements can be either field keys or array indexes.
2021+
pub fn delete_by_keypath<'a, I: Iterator<Item = &'a KeyPath<'a>>>(
2022+
value: &[u8],
2023+
keypath: I,
2024+
buf: &mut Vec<u8>,
2025+
) -> Result<(), Error> {
2026+
let mut keypath: VecDeque<_> = keypath.collect();
2027+
if !is_jsonb(value) {
2028+
let mut value = parse_value(value)?;
2029+
match value {
2030+
Value::Array(ref mut arr) => delete_value_array_by_keypath(arr, &mut keypath),
2031+
Value::Object(ref mut obj) => delete_value_object_by_keypath(obj, &mut keypath),
2032+
_ => return Err(Error::InvalidJsonType),
2033+
};
2034+
value.write_to_vec(buf);
2035+
return Ok(());
2036+
}
2037+
delete_by_keypath_jsonb(value, keypath, buf)
2038+
}
2039+
2040+
fn delete_value_array_by_keypath<'a>(
2041+
arr: &mut Vec<Value<'_>>,
2042+
keypath: &mut VecDeque<&'a KeyPath<'a>>,
2043+
) {
2044+
if let Some(KeyPath::Index(idx)) = keypath.pop_front() {
2045+
let len = arr.len() as i32;
2046+
let idx = if *idx < 0 { len - idx.abs() } else { *idx };
2047+
if idx < 0 || idx >= len {
2048+
return;
2049+
}
2050+
if keypath.is_empty() {
2051+
arr.remove(idx as usize);
2052+
} else {
2053+
match arr[idx as usize] {
2054+
Value::Array(ref mut arr) => delete_value_array_by_keypath(arr, keypath),
2055+
Value::Object(ref mut obj) => delete_value_object_by_keypath(obj, keypath),
2056+
_ => {}
2057+
}
2058+
}
2059+
}
2060+
}
2061+
2062+
fn delete_value_object_by_keypath<'a>(
2063+
obj: &mut BTreeMap<String, Value<'_>>,
2064+
keypath: &mut VecDeque<&'a KeyPath<'a>>,
2065+
) {
2066+
if let Some(path) = keypath.pop_front() {
2067+
match path {
2068+
KeyPath::QuotedName(name) | KeyPath::Name(name) => {
2069+
if keypath.is_empty() {
2070+
obj.remove(name.as_ref());
2071+
} else if let Some(val) = obj.get_mut(name.as_ref()) {
2072+
match val {
2073+
Value::Array(ref mut arr) => delete_value_array_by_keypath(arr, keypath),
2074+
Value::Object(ref mut obj) => delete_value_object_by_keypath(obj, keypath),
2075+
_ => {}
2076+
}
2077+
}
2078+
}
2079+
_ => {}
2080+
}
2081+
}
2082+
}
2083+
2084+
fn delete_by_keypath_jsonb<'a>(
2085+
value: &[u8],
2086+
mut keypath: VecDeque<&'a KeyPath<'a>>,
2087+
buf: &mut Vec<u8>,
2088+
) -> Result<(), Error> {
2089+
let header = read_u32(value, 0)?;
2090+
match header & CONTAINER_HEADER_TYPE_MASK {
2091+
ARRAY_CONTAINER_TAG => {
2092+
match delete_jsonb_array_by_keypath(value, header, &mut keypath)? {
2093+
Some(builder) => {
2094+
builder.build_into(buf);
2095+
}
2096+
None => {
2097+
buf.extend_from_slice(value);
2098+
}
2099+
};
2100+
}
2101+
OBJECT_CONTAINER_TAG => {
2102+
match delete_jsonb_object_by_keypath(value, header, &mut keypath)? {
2103+
Some(builder) => {
2104+
builder.build_into(buf);
2105+
}
2106+
None => {
2107+
buf.extend_from_slice(value);
2108+
}
2109+
}
2110+
}
2111+
_ => return Err(Error::InvalidJsonType),
2112+
}
2113+
Ok(())
2114+
}
2115+
2116+
fn delete_jsonb_array_by_keypath<'a, 'b>(
2117+
value: &'b [u8],
2118+
header: u32,
2119+
keypath: &mut VecDeque<&'a KeyPath<'a>>,
2120+
) -> Result<Option<ArrayBuilder<'b>>, Error> {
2121+
let len = (header & CONTAINER_HEADER_LEN_MASK) as i32;
2122+
match keypath.pop_front() {
2123+
Some(KeyPath::Index(idx)) => {
2124+
let idx = if *idx < 0 { len - idx.abs() } else { *idx };
2125+
if idx < 0 || idx >= len {
2126+
return Ok(None);
2127+
}
2128+
let mut builder = ArrayBuilder::new(len as usize);
2129+
let idx = idx as usize;
2130+
for (i, entry) in iterate_array(value, header).enumerate() {
2131+
if i != idx {
2132+
builder.push_raw(entry.0, entry.1);
2133+
} else if !keypath.is_empty() {
2134+
let item_value = entry.1;
2135+
match entry.0.type_code {
2136+
CONTAINER_TAG => {
2137+
let item_header = read_u32(item_value, 0)?;
2138+
match item_header & CONTAINER_HEADER_TYPE_MASK {
2139+
ARRAY_CONTAINER_TAG => {
2140+
match delete_jsonb_array_by_keypath(
2141+
item_value,
2142+
item_header,
2143+
keypath,
2144+
)? {
2145+
Some(item_builder) => builder.push_array(item_builder),
2146+
None => return Ok(None),
2147+
}
2148+
}
2149+
OBJECT_CONTAINER_TAG => {
2150+
match delete_jsonb_object_by_keypath(
2151+
item_value,
2152+
item_header,
2153+
keypath,
2154+
)? {
2155+
Some(item_builder) => builder.push_object(item_builder),
2156+
None => return Ok(None),
2157+
}
2158+
}
2159+
_ => unreachable!(),
2160+
}
2161+
}
2162+
_ => return Ok(None),
2163+
}
2164+
}
2165+
}
2166+
Ok(Some(builder))
2167+
}
2168+
_ => Ok(None),
2169+
}
2170+
}
2171+
2172+
fn delete_jsonb_object_by_keypath<'a, 'b>(
2173+
value: &'b [u8],
2174+
header: u32,
2175+
keypath: &mut VecDeque<&'a KeyPath<'a>>,
2176+
) -> Result<Option<ObjectBuilder<'b>>, Error> {
2177+
match keypath.pop_front() {
2178+
Some(path) => match path {
2179+
KeyPath::QuotedName(name) | KeyPath::Name(name) => {
2180+
let mut builder = ObjectBuilder::new();
2181+
for (key, jentry, item) in iterate_object_entries(value, header) {
2182+
if !key.eq(name) {
2183+
builder.push_raw(key, jentry, item);
2184+
} else if !keypath.is_empty() {
2185+
match jentry.type_code {
2186+
CONTAINER_TAG => {
2187+
let item_header = read_u32(item, 0)?;
2188+
match item_header & CONTAINER_HEADER_TYPE_MASK {
2189+
ARRAY_CONTAINER_TAG => match delete_jsonb_array_by_keypath(
2190+
item,
2191+
item_header,
2192+
keypath,
2193+
)? {
2194+
Some(item_builder) => builder.push_array(key, item_builder),
2195+
None => return Ok(None),
2196+
},
2197+
OBJECT_CONTAINER_TAG => {
2198+
match delete_jsonb_object_by_keypath(
2199+
item,
2200+
item_header,
2201+
keypath,
2202+
)? {
2203+
Some(item_builder) => {
2204+
builder.push_object(key, item_builder)
2205+
}
2206+
None => return Ok(None),
2207+
}
2208+
}
2209+
_ => unreachable!(),
2210+
}
2211+
}
2212+
_ => return Ok(None),
2213+
}
2214+
}
2215+
}
2216+
Ok(Some(builder))
2217+
}
2218+
_ => Ok(None),
2219+
},
2220+
None => Ok(None),
2221+
}
2222+
}
2223+
20182224
/// Deletes a key (and its value) from a JSON object, or matching string value(s) from a JSON array.
20192225
pub fn delete_by_name(value: &[u8], name: &str, buf: &mut Vec<u8>) -> Result<(), Error> {
20202226
if !is_jsonb(value) {
@@ -2166,7 +2372,7 @@ fn strip_nulls_array(header: u32, value: &[u8]) -> Result<ArrayBuilder<'_>, Erro
21662372
for (jentry, item) in iterate_array(value, header) {
21672373
match jentry.type_code {
21682374
CONTAINER_TAG => {
2169-
let item_header = read_u32(item, 0).unwrap();
2375+
let item_header = read_u32(item, 0)?;
21702376
match item_header & CONTAINER_HEADER_TYPE_MASK {
21712377
OBJECT_CONTAINER_TAG => {
21722378
builder.push_object(strip_nulls_object(item_header, item)?);
@@ -2188,7 +2394,7 @@ fn strip_nulls_object(header: u32, value: &[u8]) -> Result<ObjectBuilder<'_>, Er
21882394
for (key, jentry, item) in iterate_object_entries(value, header) {
21892395
match jentry.type_code {
21902396
CONTAINER_TAG => {
2191-
let item_header = read_u32(item, 0).unwrap();
2397+
let item_header = read_u32(item, 0)?;
21922398
match item_header & CONTAINER_HEADER_TYPE_MASK {
21932399
OBJECT_CONTAINER_TAG => {
21942400
builder.push_object(key, strip_nulls_object(item_header, item)?);

tests/it/functions.rs

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,12 @@ use std::collections::BTreeMap;
1818

1919
use jsonb::{
2020
array_length, array_values, as_bool, as_null, as_number, as_str, build_array, build_object,
21-
compare, concat, contains, convert_to_comparable, delete_by_index, delete_by_name,
22-
exists_all_keys, exists_any_keys, from_slice, get_by_index, get_by_keypath, get_by_name,
23-
get_by_path, is_array, is_object, keypath::parse_key_paths, object_each, object_keys,
24-
parse_value, path_exists, path_match, strip_nulls, to_bool, to_f64, to_i64, to_pretty_string,
25-
to_str, to_string, to_u64, traverse_check_string, type_of, Error, Number, Object, Value,
21+
compare, concat, contains, convert_to_comparable, delete_by_index, delete_by_keypath,
22+
delete_by_name, exists_all_keys, exists_any_keys, from_slice, get_by_index, get_by_keypath,
23+
get_by_name, get_by_path, is_array, is_object, keypath::parse_key_paths, object_each,
24+
object_keys, parse_value, path_exists, path_match, strip_nulls, to_bool, to_f64, to_i64,
25+
to_pretty_string, to_str, to_string, to_u64, traverse_check_string, type_of, Error, Number,
26+
Object, Value,
2627
};
2728

2829
use jsonb::jsonpath::parse_json_path;
@@ -1416,6 +1417,53 @@ fn test_delete_by_index_errors() {
14161417
}
14171418
}
14181419

1420+
#[test]
1421+
fn test_delete_by_keypath() {
1422+
let sources = vec![
1423+
(r#"{"a":1,"b":[1,2,3]}"#, "{}", r#"{"a":1,"b":[1,2,3]}"#),
1424+
(r#"{"a":1,"b":[1,2,3]}"#, "{b}", r#"{"a":1}"#),
1425+
(r#"{"a":1,"b":[1,2,3]}"#, "{b,2}", r#"{"a":1,"b":[1,2]}"#),
1426+
(r#"{"a":1,"b":[1,2,3]}"#, "{b,-2}", r#"{"a":1,"b":[1,3]}"#),
1427+
(
1428+
r#"{"a":1,"b":[{"c":1,"d":10},2,3]}"#,
1429+
"{b,0,d}",
1430+
r#"{"a":1,"b":[{"c":1},2,3]}"#,
1431+
),
1432+
(r#"{"a":1,"b":[1,2,3]}"#, "{b,20}", r#"{"a":1,"b":[1,2,3]}"#),
1433+
(
1434+
r#"{"a":1,"b":[1,2,3]}"#,
1435+
"{b,20,c,e}",
1436+
r#"{"a":1,"b":[1,2,3]}"#,
1437+
),
1438+
];
1439+
for (json, keypath, result) in sources {
1440+
{
1441+
let json = json.as_bytes();
1442+
let keypath = parse_key_paths(keypath.as_bytes()).unwrap();
1443+
let mut buf = Vec::new();
1444+
1445+
delete_by_keypath(json, keypath.paths.iter(), &mut buf).unwrap();
1446+
1447+
let actual = from_slice(&buf).unwrap();
1448+
let expected = parse_value(result.as_bytes()).unwrap();
1449+
1450+
assert_eq!(actual, expected);
1451+
}
1452+
{
1453+
let json = parse_value(json.as_bytes()).unwrap().to_vec();
1454+
let keypath = parse_key_paths(keypath.as_bytes()).unwrap();
1455+
let mut buf = Vec::new();
1456+
1457+
delete_by_keypath(&json, keypath.paths.iter(), &mut buf).unwrap();
1458+
1459+
let actual = from_slice(&buf).unwrap();
1460+
let expected = parse_value(result.as_bytes()).unwrap();
1461+
1462+
assert_eq!(actual, expected);
1463+
}
1464+
}
1465+
}
1466+
14191467
fn init_object<'a>(entries: Vec<(&str, Value<'a>)>) -> Value<'a> {
14201468
let mut map = BTreeMap::new();
14211469
for (key, val) in entries {

0 commit comments

Comments
 (0)