Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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());
assert!(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());
assert!(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());
assert!(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());
assert!(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