Skip to content

Commit

Permalink
feat: add delete_by_index & delete_by_name
Browse files Browse the repository at this point in the history
  • Loading branch information
akoshchiy committed Jan 13, 2024
1 parent a0669bf commit 9ee688f
Show file tree
Hide file tree
Showing 5 changed files with 233 additions and 6 deletions.
2 changes: 2 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ pub enum Error {
InvalidJsonPathPredicate,
InvalidKeyPath,

InvalidJsonType,

Syntax(ParseErrorCode, usize),
}

Expand Down
98 changes: 98 additions & 0 deletions src/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2015,6 +2015,104 @@ fn concat_jsonb(left: &[u8], right: &[u8], buf: &mut Vec<u8>) -> Result<(), Erro
Ok(())
}

/// Deletes a key (and its value) from a JSON object, or matching string value(s) from a JSON array.
pub fn delete_by_name(value: &[u8], name: &str, buf: &mut Vec<u8>) -> Result<(), Error> {
if !is_jsonb(value) {
let mut val = parse_value(value)?;
match &mut val {
Value::Array(arr) => {
arr.retain(|item| !matches!(item, Value::String(v) if v.eq(name)));
}
Value::Object(obj) => {
obj.remove(name);
}
_ => return Err(Error::InvalidJsonType),
};
val.write_to_vec(buf);
return Ok(());
}
delete_jsonb_by_name(value, name, buf)
}

fn delete_jsonb_by_name(value: &[u8], name: &str, buf: &mut Vec<u8>) -> Result<(), Error> {
let header = read_u32(value, 0)?;

match header & CONTAINER_HEADER_TYPE_MASK {
OBJECT_CONTAINER_TAG => {
let mut builder = ObjectBuilder::new();
for (key, jentry, item) in iterate_object_entries(value, header) {
if !key.eq(name) {
builder.push_raw(key, jentry, item);
}
}
builder.build_into(buf);
}
ARRAY_CONTAINER_TAG => {
let mut builder = ArrayBuilder::new((header & CONTAINER_HEADER_LEN_MASK) as usize);
for (jentry, item) in iterate_array(value, header) {
let matches = match jentry.type_code {
STRING_TAG => {
let v = unsafe { from_utf8_unchecked(item) };
v.eq(name)
}
_ => false,
};
if !matches {
builder.push_raw(jentry, item);
}
}
builder.build_into(buf);
}
_ => return Err(Error::InvalidJsonType),
}
Ok(())
}

/// Deletes the array element with specified index (negative integers count from the end).
pub fn delete_by_index(value: &[u8], index: i32, buf: &mut Vec<u8>) -> Result<(), Error> {
if !is_jsonb(value) {
let mut val = parse_value(value)?;
match &mut val {
Value::Array(arr) => {
let len = arr.len() as i32;
let index = if index < 0 { len - index.abs() } else { index };
if index >= 0 && index < len {
arr.remove(index as usize);
}
}
_ => return Err(Error::InvalidJsonType),
};
val.write_to_vec(buf);
return Ok(());
}
delete_jsonb_by_index(value, index, buf)
}

fn delete_jsonb_by_index(value: &[u8], index: i32, buf: &mut Vec<u8>) -> Result<(), Error> {
let header = read_u32(value, 0)?;

match header & CONTAINER_HEADER_TYPE_MASK {
ARRAY_CONTAINER_TAG => {
let len = (header & CONTAINER_HEADER_LEN_MASK) as i32;
let index = if index < 0 { len - index.abs() } else { index };
if index < 0 || index >= len {
buf.extend_from_slice(value);
} else {
let mut builder = ArrayBuilder::new((header & CONTAINER_HEADER_LEN_MASK) as usize);
let index = index as usize;
for (i, entry) in iterate_array(value, header).enumerate() {
if i != index {
builder.push_raw(entry.0, entry.1);
}
}
builder.build_into(buf);
}
}
_ => return Err(Error::InvalidJsonType),
}
Ok(())
}

/// Deletes all object fields that have null values from the given JSON value, recursively.
/// Null values that are not object fields are untouched.
pub fn strip_nulls(value: &[u8], buf: &mut Vec<u8>) -> Result<(), Error> {
Expand Down
2 changes: 1 addition & 1 deletion src/jsonpath/selector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,7 @@ impl<'a> Selector<'a> {
Expr::Paths(paths) => {
// get value from path and convert to `ExprValue`.
let mut poses = VecDeque::new();
if let Some(Path::Current) = paths.get(0) {
if let Some(Path::Current) = paths.first() {
poses.push_back(pos.clone());
} else {
poses.push_back(Position::Container((0, root.len())));
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ mod value;

pub use de::from_slice;
pub use error::Error;
#[allow(unused_imports)]
pub use from::*;
pub use functions::*;
pub use number::Number;
Expand Down
136 changes: 131 additions & 5 deletions tests/it/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ use std::collections::BTreeMap;

use jsonb::{
array_length, array_values, as_bool, as_null, as_number, as_str, build_array, build_object,
compare, concat, contains, convert_to_comparable, exists_all_keys, exists_any_keys, from_slice,
get_by_index, get_by_keypath, get_by_name, get_by_path, is_array, is_object,
keypath::parse_key_paths, object_each, object_keys, parse_value, path_exists, path_match,
strip_nulls, to_bool, to_f64, to_i64, to_pretty_string, to_str, to_string, to_u64,
traverse_check_string, type_of, Number, Object, Value,
compare, concat, contains, convert_to_comparable, delete_by_index, delete_by_name,
exists_all_keys, exists_any_keys, from_slice, get_by_index, get_by_keypath, get_by_name,
get_by_path, is_array, is_object, keypath::parse_key_paths, object_each, object_keys,
parse_value, path_exists, path_match, strip_nulls, to_bool, to_f64, to_i64, to_pretty_string,
to_str, to_string, to_u64, traverse_check_string, type_of, Error, Number, Object, Value,
};

use jsonb::jsonpath::parse_json_path;
Expand Down Expand Up @@ -1290,6 +1290,132 @@ fn test_concat() {
}
}

#[test]
fn test_delete_by_name() {
let sources = vec![
("[1,2,3]", "1", "[1,2,3]"),
(r#"["1","2","3"]"#, "0", r#"["1","2","3"]"#),
(r#"["1","2","3"]"#, "1", r#"["2","3"]"#),
(
r#"["1","2","3",{"a":1,"b":2}]"#,
"1",
r#"["2","3",{"a":1,"b":2}]"#,
),
(r#"{"a":1,"b":2}"#, "c", r#"{"a":1,"b":2}"#),
(r#"{"a":1,"b":2}"#, "a", r#"{"b":2}"#),
(r#"{"b":2}"#, "b", "{}"),
];
for (json, name, result) in sources {
{
let mut buf = Vec::new();
delete_by_name(json.as_bytes(), name, &mut buf).unwrap();

let actual = from_slice(&buf).unwrap();
let expected = parse_value(result.as_bytes()).unwrap();

assert_eq!(actual, expected);
}
{
let json = parse_value(json.as_bytes()).unwrap().to_vec();
let mut buf = Vec::new();

delete_by_name(&json, name, &mut buf).unwrap();

let actual = from_slice(&buf).unwrap();
let expected = parse_value(result.as_bytes()).unwrap();

assert_eq!(actual, expected);
}
}
}

#[test]
fn test_delete_by_name_errors() {
let sources = vec![(r#""asd""#, "asd"), ("true", "true"), ("1", "1")];
for (json, name) in sources {
{
let mut buf = Vec::new();
let result = delete_by_name(json.as_bytes(), name, &mut buf);

assert!(result.is_err());
matches!(result.unwrap_err(), Error::InvalidJsonType);
}
{
let json = parse_value(json.as_bytes()).unwrap().to_vec();
let mut buf = Vec::new();

let result = delete_by_name(&json, name, &mut buf);

assert!(result.is_err());
matches!(result.unwrap_err(), Error::InvalidJsonType);
}
}
}

#[test]
fn test_delete_by_index() {
let sources = vec![
("[1,2,3]", 0, "[2,3]"),
("[1,2,3]", 1, "[1,3]"),
("[1,2,3]", 2, "[1,2]"),
("[1,2,3]", -1, "[1,2]"),
("[1,2,3]", -2, "[1,3]"),
("[1,2,3]", -3, "[2,3]"),
("[1,2,3]", -4, "[1,2,3]"),
(r#"[1,2,{"a":[1,2,3],"b":[40,50,60]}]"#, 2, "[1,2]"),
];
for (json, index, result) in sources {
{
let mut buf = Vec::new();
delete_by_index(json.as_bytes(), index, &mut buf).unwrap();

let actual = from_slice(&buf).unwrap();
let expected = parse_value(result.as_bytes()).unwrap();

assert_eq!(actual, expected);
}
{
let json = parse_value(json.as_bytes()).unwrap().to_vec();
let mut buf = Vec::new();

delete_by_index(&json, index, &mut buf).unwrap();

let actual = from_slice(&buf).unwrap();
let expected = parse_value(result.as_bytes()).unwrap();

assert_eq!(actual, expected);
}
}
}

#[test]
fn test_delete_by_index_errors() {
let sources = vec![
(r#""asd""#, 1),
("true", 0),
("1", 10),
(r#"{"a":1,"b":2}"#, 20),
];
for (json, index) in sources {
{
let mut buf = Vec::new();
let result = delete_by_index(json.as_bytes(), index, &mut buf);

assert!(result.is_err());
matches!(result.unwrap_err(), Error::InvalidJsonType);
}
{
let json = parse_value(json.as_bytes()).unwrap().to_vec();
let mut buf = Vec::new();

let result = delete_by_index(&json, index, &mut buf);

assert!(result.is_err());
matches!(result.unwrap_err(), Error::InvalidJsonType);
}
}
}

fn init_object<'a>(entries: Vec<(&str, Value<'a>)>) -> Value<'a> {
let mut map = BTreeMap::new();
for (key, val) in entries {
Expand Down

0 comments on commit 9ee688f

Please sign in to comment.