use crate::error::{Error, Result};
use sonic_rs::{JsonContainerTrait, JsonValueMutTrait, JsonValueTrait, Value};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SliceIndex {
Index(isize),
Slice {
start: Option<isize>,
stop: Option<isize>,
step: Option<isize>,
},
}
#[derive(Debug, Clone, PartialEq)]
pub enum FilterOp {
Exists,
NotExists,
Eq(Value),
Ne(Value),
Lt(f64),
Le(f64),
Gt(f64),
Ge(f64),
}
#[derive(Debug, Clone, PartialEq)]
pub struct FilterExpr {
pub path: Vec<String>,
pub op: FilterOp,
}
#[derive(Debug, Clone, PartialEq)]
pub enum PathSegment {
Root,
Field(String),
MultiField(Vec<String>),
Index(isize),
MultiIndex(Vec<SliceIndex>),
Wildcard,
Filter(FilterExpr),
Recursive(Box<PathSegment>),
}
pub fn parse_json_path(path: &str) -> Result<Vec<PathSegment>> {
let s = path.trim();
if s.is_empty() || s == "$" || s == "." {
return Ok(vec![PathSegment::Root]);
}
let bytes = s.as_bytes();
let mut i = 0;
let mut segments = Vec::new();
if bytes[0] == b'$' {
segments.push(PathSegment::Root);
i += 1;
}
while i < bytes.len() {
if bytes[i] == b'.' {
i += 1;
if i >= bytes.len() {
return Err(Error::invalid_data("Invalid JSONPath: trailing dot"));
}
if bytes[i] == b'.' {
i += 1;
if i >= bytes.len() {
return Err(Error::invalid_data(
"Invalid JSONPath: trailing recursive descent '..'",
));
}
if bytes[i] == b'*' {
segments.push(PathSegment::Recursive(Box::new(PathSegment::Wildcard)));
i += 1;
continue;
}
if bytes[i] == b'[' {
let bracket_seg = parse_bracket(s, &mut i)?;
segments.push(PathSegment::Recursive(Box::new(bracket_seg)));
continue;
}
let start = i;
while i < bytes.len() && bytes[i] != b'.' && bytes[i] != b'[' {
i += 1;
}
let name = s[start..i].trim();
if name.is_empty() {
return Err(Error::invalid_data(
"Invalid JSONPath: empty identifier after '..'",
));
}
segments.push(PathSegment::Recursive(Box::new(PathSegment::Field(
name.to_string(),
))));
continue;
}
if bytes[i] == b'*' {
segments.push(PathSegment::Wildcard);
i += 1;
continue;
}
if bytes[i] == b'[' {
let bracket_seg = parse_bracket(s, &mut i)?;
segments.push(bracket_seg);
continue;
}
let start = i;
while i < bytes.len() && bytes[i] != b'.' && bytes[i] != b'[' {
i += 1;
}
let name = s[start..i].trim();
if name.is_empty() {
return Err(Error::invalid_data(
"Invalid JSONPath: empty identifier after '.'",
));
}
if name == "*" {
segments.push(PathSegment::Wildcard);
} else {
segments.push(PathSegment::Field(name.to_string()));
}
} else if bytes[i] == b'[' {
let bracket_seg = parse_bracket(s, &mut i)?;
segments.push(bracket_seg);
} else {
let start = i;
while i < bytes.len() && bytes[i] != b'.' && bytes[i] != b'[' {
i += 1;
}
let name = s[start..i].trim();
if name.is_empty() {
return Err(Error::invalid_data("Invalid JSONPath: unexpected token"));
}
if name == "*" {
segments.push(PathSegment::Wildcard);
} else {
segments.push(PathSegment::Field(name.to_string()));
}
}
}
if segments.is_empty() {
Ok(vec![PathSegment::Root])
} else {
Ok(segments)
}
}
fn parse_bracket(s: &str, i: &mut usize) -> Result<PathSegment> {
let bytes = s.as_bytes();
if *i >= bytes.len() || bytes[*i] != b'[' {
return Err(Error::invalid_data("Expected '['"));
}
*i += 1;
let start = *i;
let mut depth = 1;
let mut in_single_quote = false;
let mut in_double_quote = false;
while *i < bytes.len() && depth > 0 {
match bytes[*i] {
b'\'' if !in_double_quote => {
in_single_quote = !in_single_quote;
}
b'"' if !in_single_quote => {
in_double_quote = !in_double_quote;
}
b'[' if !in_single_quote && !in_double_quote => {
depth += 1;
}
b']' if !in_single_quote && !in_double_quote => {
depth -= 1;
}
_ => {}
}
if depth > 0 {
*i += 1;
}
}
if depth > 0 || *i >= bytes.len() || bytes[*i] != b']' {
return Err(Error::invalid_data("Invalid JSONPath: unclosed bracket"));
}
let inner = s[start..*i].trim();
*i += 1;
parse_bracket_content(inner)
}
fn parse_bracket_content(inner: &str) -> Result<PathSegment> {
let inner = inner.trim();
if inner.is_empty() || inner == "*" {
return Ok(PathSegment::Wildcard);
}
if let Some(stripped) = inner.strip_prefix('?') {
let expr_str = stripped
.trim()
.trim_start_matches('(')
.trim_end_matches(')')
.trim();
if let Some(filter) = parse_filter_expr(expr_str) {
return Ok(PathSegment::Filter(filter));
}
return Err(Error::invalid_data("Invalid filter expression in JSONPath"));
}
let parts = split_bracket_parts(inner);
if parts.len() > 1 {
let mut slice_indices = Vec::new();
let mut field_names = Vec::new();
let mut all_indices = true;
let mut all_fields = true;
for part in &parts {
let p = part.trim();
if let Some(slice_idx) = parse_single_slice(p) {
slice_indices.push(slice_idx);
all_fields = false;
} else {
let unquoted = unquote_str(p);
field_names.push(unquoted);
all_indices = false;
}
}
if all_indices {
return Ok(PathSegment::MultiIndex(slice_indices));
}
if all_fields {
return Ok(PathSegment::MultiField(field_names));
}
return Ok(PathSegment::MultiField(
parts.into_iter().map(|p| unquote_str(&p)).collect(),
));
}
if let Some(slice_idx) = parse_single_slice(inner) {
match slice_idx {
SliceIndex::Index(idx) => return Ok(PathSegment::Index(idx)),
SliceIndex::Slice { start, stop, step } => {
return Ok(PathSegment::MultiIndex(vec![SliceIndex::Slice {
start,
stop,
step,
}]));
}
}
}
let unquoted = unquote_str(inner);
if !unquoted.is_empty() {
Ok(PathSegment::Field(unquoted))
} else {
Err(Error::invalid_data("Invalid empty bracket content"))
}
}
fn split_bracket_parts(s: &str) -> Vec<String> {
let mut parts = Vec::new();
let mut cur = String::new();
let mut in_single_quote = false;
let mut in_double_quote = false;
for c in s.chars() {
match c {
'\'' if !in_double_quote => {
in_single_quote = !in_single_quote;
cur.push(c);
}
'"' if !in_single_quote => {
in_double_quote = !in_double_quote;
cur.push(c);
}
',' if !in_single_quote && !in_double_quote => {
parts.push(cur.trim().to_string());
cur.clear();
}
_ => cur.push(c),
}
}
if !cur.trim().is_empty() {
parts.push(cur.trim().to_string());
}
parts
}
fn unquote_str(s: &str) -> String {
let s = s.trim();
if (s.starts_with('\'') && s.ends_with('\'') && s.len() >= 2)
|| (s.starts_with('"') && s.ends_with('"') && s.len() >= 2)
{
s[1..s.len() - 1]
.replace("\\'", "'")
.replace("\\\"", "\"")
.replace("\\\\", "\\")
} else {
s.to_string()
}
}
fn parse_single_slice(s: &str) -> Option<SliceIndex> {
let s = s.trim();
if s.contains(':') {
let parts: Vec<&str> = s.split(':').collect();
let start = parts.first().and_then(|p| p.trim().parse::<isize>().ok());
let stop = parts.get(1).and_then(|p| p.trim().parse::<isize>().ok());
let step = parts.get(2).and_then(|p| p.trim().parse::<isize>().ok());
Some(SliceIndex::Slice { start, stop, step })
} else {
s.parse::<isize>().ok().map(SliceIndex::Index)
}
}
fn parse_filter_expr(s: &str) -> Option<FilterExpr> {
let s = s.trim();
if let Some(stripped) = s.strip_prefix('!') {
let inner = stripped.trim().strip_prefix('@')?;
let inner = inner.trim().strip_prefix('.').unwrap_or(inner);
let path = inner.split('.').map(|p| p.to_string()).collect();
return Some(FilterExpr {
path,
op: FilterOp::NotExists,
});
}
for op_str in &["==", "!=", "<=", ">=", "<", ">"] {
if let Some(idx) = s.find(op_str) {
let left = s[..idx].trim();
let right = s[idx + op_str.len()..].trim();
let left_path = left
.strip_prefix('@')?
.trim()
.strip_prefix('.')
.unwrap_or(left)
.split('.')
.map(|p| p.to_string())
.collect();
let right_val = if (right.starts_with('\'') && right.ends_with('\''))
|| (right.starts_with('"') && right.ends_with('"'))
{
sonic_rs::json!(unquote_str(right))
} else if right == "true" {
sonic_rs::json!(true)
} else if right == "false" {
sonic_rs::json!(false)
} else if right == "null" {
sonic_rs::json!(null)
} else if let Ok(num) = right.parse::<f64>() {
if num.fract() == 0.0 && num >= (i64::MIN as f64) && num <= (i64::MAX as f64) {
sonic_rs::json!(num as i64)
} else {
sonic_rs::json!(num)
}
} else {
sonic_rs::json!(right)
};
let op = match *op_str {
"==" => FilterOp::Eq(right_val),
"!=" => FilterOp::Ne(right_val),
"<" => FilterOp::Lt(right.parse().unwrap_or(0.0)),
"<=" => FilterOp::Le(right.parse().unwrap_or(0.0)),
">" => FilterOp::Gt(right.parse().unwrap_or(0.0)),
">=" => FilterOp::Ge(right.parse().unwrap_or(0.0)),
_ => FilterOp::Exists,
};
return Some(FilterExpr {
path: left_path,
op,
});
}
}
let left = s.strip_prefix('@')?;
let left = left.trim().strip_prefix('.').unwrap_or(left);
let path = left.split('.').map(|p| p.to_string()).collect();
Some(FilterExpr {
path,
op: FilterOp::Exists,
})
}
fn eval_filter_expr(val: &Value, filter: &FilterExpr) -> bool {
let mut cur = val;
for seg in &filter.path {
if let Some(obj) = cur.as_object() {
if let Some(next) = obj.get(seg) {
cur = next;
} else {
return matches!(filter.op, FilterOp::NotExists);
}
} else {
return matches!(filter.op, FilterOp::NotExists);
}
}
match &filter.op {
FilterOp::Exists => !cur.is_null() && cur.as_bool().unwrap_or(true),
FilterOp::NotExists => cur.is_null() || !cur.as_bool().unwrap_or(true),
FilterOp::Eq(target) => {
if let (Some(a), Some(b)) = (cur.as_f64(), target.as_f64()) {
a == b
} else {
cur == target
}
}
FilterOp::Ne(target) => {
if let (Some(a), Some(b)) = (cur.as_f64(), target.as_f64()) {
a != b
} else {
cur != target
}
}
FilterOp::Lt(target) => cur.as_f64().is_some_and(|v| v < *target),
FilterOp::Le(target) => cur.as_f64().is_some_and(|v| v <= *target),
FilterOp::Gt(target) => cur.as_f64().is_some_and(|v| v > *target),
FilterOp::Ge(target) => cur.as_f64().is_some_and(|v| v >= *target),
}
}
pub fn eval_slice(
arr: &[Value],
start: Option<isize>,
stop: Option<isize>,
step: Option<isize>,
) -> Vec<&Value> {
let len = arr.len() as isize;
if len == 0 {
return Vec::new();
}
let step_val = step.unwrap_or(1);
if step_val == 0 {
return Vec::new();
}
let mut result = Vec::new();
if step_val > 0 {
let s = match start {
Some(v) if v < 0 => (len + v).max(0),
Some(v) => v.min(len),
None => 0,
};
let e = match stop {
Some(v) if v < 0 => (len + v).max(0),
Some(v) => v.min(len),
None => len,
};
let mut idx = s;
while idx < e {
if idx >= 0 && (idx as usize) < arr.len() {
result.push(&arr[idx as usize]);
}
idx += step_val;
}
} else {
let s = match start {
Some(v) if v < 0 => len + v,
Some(v) => v.min(len - 1),
None => len - 1,
};
let e = match stop {
Some(v) if v < 0 => len + v,
Some(v) => v,
None => -1,
};
let mut idx = s;
while idx > e {
if idx >= 0 && (idx as usize) < arr.len() {
result.push(&arr[idx as usize]);
}
idx += step_val;
}
}
result
}
pub fn get_path_values<'a>(root: &'a Value, path: &str) -> Result<Vec<&'a Value>> {
let segments = parse_json_path(path)?;
let mut current = vec![root];
for seg in &segments {
match seg {
PathSegment::Root => {}
PathSegment::Field(name) => {
let mut next = Vec::new();
for node in current {
if let Some(obj) = node.as_object()
&& let Some(v) = obj.get(name)
{
next.push(v);
}
}
current = next;
}
PathSegment::MultiField(names) => {
let mut next = Vec::new();
for node in current {
if let Some(obj) = node.as_object() {
for name in names {
if let Some(v) = obj.get(name) {
next.push(v);
}
}
}
}
current = next;
}
PathSegment::Index(idx) => {
let mut next = Vec::new();
for node in current {
if let Some(arr) = node.as_array() {
let actual_idx = if *idx < 0 {
arr.len() as isize + *idx
} else {
*idx
};
if actual_idx >= 0 && (actual_idx as usize) < arr.len() {
next.push(&arr[actual_idx as usize]);
}
}
}
current = next;
}
PathSegment::MultiIndex(slices) => {
let mut next = Vec::new();
for node in current {
if let Some(arr) = node.as_array() {
for item in slices {
match item {
SliceIndex::Index(idx) => {
let actual_idx = if *idx < 0 {
arr.len() as isize + *idx
} else {
*idx
};
if actual_idx >= 0 && (actual_idx as usize) < arr.len() {
next.push(&arr[actual_idx as usize]);
}
}
SliceIndex::Slice { start, stop, step } => {
let elements = eval_slice(arr, *start, *stop, *step);
next.extend(elements);
}
}
}
}
}
current = next;
}
PathSegment::Wildcard => {
let mut next = Vec::new();
for node in current {
if let Some(obj) = node.as_object() {
for (_, v) in obj.iter() {
next.push(v);
}
} else if let Some(arr) = node.as_array() {
for v in arr.iter() {
next.push(v);
}
}
}
current = next;
}
PathSegment::Filter(filter) => {
let mut next = Vec::new();
for node in current {
if let Some(arr) = node.as_array() {
for item in arr.iter() {
if eval_filter_expr(item, filter) {
next.push(item);
}
}
} else if eval_filter_expr(node, filter) {
next.push(node);
}
}
current = next;
}
PathSegment::Recursive(inner) => {
let mut next = Vec::new();
for node in current {
collect_recursive_matching(node, inner, &mut next);
}
current = next;
}
}
}
Ok(current)
}
fn collect_recursive_matching<'a>(node: &'a Value, target: &PathSegment, out: &mut Vec<&'a Value>) {
match target {
PathSegment::Field(name) => {
if let Some(obj) = node.as_object()
&& let Some(v) = obj.get(name)
{
out.push(v);
}
}
PathSegment::Index(idx) => {
if let Some(arr) = node.as_array() {
let len = arr.len() as isize;
let actual = if *idx < 0 { len + *idx } else { *idx };
if actual >= 0 && (actual as usize) < arr.len() {
out.push(&arr[actual as usize]);
}
}
}
PathSegment::Wildcard => {
if let Some(obj) = node.as_object() {
for (_, v) in obj.iter() {
out.push(v);
}
} else if let Some(arr) = node.as_array() {
for v in arr.iter() {
out.push(v);
}
}
}
PathSegment::Filter(filter) => {
if let Some(arr) = node.as_array() {
for item in arr.iter() {
if eval_filter_expr(item, filter) {
out.push(item);
}
}
} else if eval_filter_expr(node, filter) {
out.push(node);
}
}
PathSegment::MultiField(names) => {
if let Some(obj) = node.as_object() {
for name in names {
if let Some(v) = obj.get(name) {
out.push(v);
}
}
}
}
PathSegment::MultiIndex(slices) => {
if let Some(arr) = node.as_array() {
for item in slices {
match item {
SliceIndex::Index(idx) => {
let len = arr.len() as isize;
let actual = if *idx < 0 { len + *idx } else { *idx };
if actual >= 0 && (actual as usize) < arr.len() {
out.push(&arr[actual as usize]);
}
}
SliceIndex::Slice { start, stop, step } => {
let elements = eval_slice(arr, *start, *stop, *step);
out.extend(elements);
}
}
}
}
}
_ => {}
}
if let Some(obj) = node.as_object() {
for (_, child) in obj.iter() {
collect_recursive_matching(child, target, out);
}
} else if let Some(arr) = node.as_array() {
for child in arr.iter() {
collect_recursive_matching(child, target, out);
}
}
}
pub fn mutate_path_values<F>(root: &mut Value, path: &str, mut f: F) -> Result<usize>
where
F: FnMut(&mut Value),
{
let segments = parse_json_path(path)?;
Ok(mutate_recursive(root, &segments, &mut f))
}
fn mutate_recursive<F>(node: &mut Value, segments: &[PathSegment], f: &mut F) -> usize
where
F: FnMut(&mut Value),
{
if segments.is_empty() || (segments.len() == 1 && segments[0] == PathSegment::Root) {
f(node);
return 1;
}
let rest = if segments[0] == PathSegment::Root {
&segments[1..]
} else {
&segments[0..]
};
if rest.is_empty() {
f(node);
return 1;
}
let head = &rest[0];
let tail = &rest[1..];
let mut count = 0;
match head {
PathSegment::Root => mutate_recursive(node, tail, f),
PathSegment::Field(name) => {
if tail.is_empty() {
if let Some(obj) = node.as_object_mut()
&& let Some(v) = obj.get_mut(name)
{
f(v);
count += 1;
}
} else if let Some(obj) = node.as_object_mut()
&& let Some(v) = obj.get_mut(name)
{
count += mutate_recursive(v, tail, f);
}
count
}
PathSegment::MultiField(names) => {
if let Some(obj) = node.as_object_mut() {
for name in names {
if tail.is_empty() {
if let Some(v) = obj.get_mut(name) {
f(v);
count += 1;
}
} else if let Some(v) = obj.get_mut(name) {
count += mutate_recursive(v, tail, f);
}
}
}
count
}
PathSegment::Index(idx) => {
if let Some(arr) = node.as_array_mut() {
let actual_idx = if *idx < 0 {
arr.len() as isize + *idx
} else {
*idx
};
if actual_idx >= 0 && (actual_idx as usize) < arr.len() {
let v = &mut arr[actual_idx as usize];
if tail.is_empty() {
f(v);
count += 1;
} else {
count += mutate_recursive(v, tail, f);
}
}
}
count
}
PathSegment::MultiIndex(slices) => {
if let Some(arr) = node.as_array_mut() {
let mut target_indices = Vec::new();
let len = arr.len() as isize;
for item in slices {
match item {
SliceIndex::Index(idx) => {
let actual_idx = if *idx < 0 { len + *idx } else { *idx };
if actual_idx >= 0 && actual_idx < len {
target_indices.push(actual_idx as usize);
}
}
SliceIndex::Slice { start, stop, step } => {
let step_val = step.unwrap_or(1);
if step_val != 0 {
let s = match start {
Some(v) if *v < 0 => (len + *v).max(0),
Some(v) => (*v).min(len),
None => 0,
};
let e = match stop {
Some(v) if *v < 0 => (len + *v).max(0),
Some(v) => (*v).min(len),
None => len,
};
let mut i = s;
while i < e {
if i >= 0 && i < len {
target_indices.push(i as usize);
}
i += step_val;
}
}
}
}
}
for idx in target_indices {
if idx < arr.len() {
let v = &mut arr[idx];
if tail.is_empty() {
f(v);
count += 1;
} else {
count += mutate_recursive(v, tail, f);
}
}
}
}
count
}
PathSegment::Wildcard => {
if let Some(obj) = node.as_object_mut() {
for (_, v) in obj.iter_mut() {
if tail.is_empty() {
f(v);
count += 1;
} else {
count += mutate_recursive(v, tail, f);
}
}
} else if let Some(arr) = node.as_array_mut() {
for v in arr.iter_mut() {
if tail.is_empty() {
f(v);
count += 1;
} else {
count += mutate_recursive(v, tail, f);
}
}
}
count
}
PathSegment::Filter(filter) => {
if let Some(arr) = node.as_array_mut() {
for item in arr.iter_mut() {
if eval_filter_expr(item, filter) {
if tail.is_empty() {
f(item);
count += 1;
} else {
count += mutate_recursive(item, tail, f);
}
}
}
}
count
}
PathSegment::Recursive(inner) => {
count += mutate_recursive_descent(node, inner, tail, f);
count
}
}
}
fn mutate_recursive_descent<F>(
node: &mut Value,
target: &PathSegment,
tail: &[PathSegment],
f: &mut F,
) -> usize
where
F: FnMut(&mut Value),
{
let mut count = 0;
match target {
PathSegment::Field(name) => {
if let Some(obj) = node.as_object_mut()
&& let Some(v) = obj.get_mut(name)
{
if tail.is_empty() {
f(v);
count += 1;
} else {
count += mutate_recursive(v, tail, f);
}
}
}
PathSegment::Index(idx) => {
if let Some(arr) = node.as_array_mut() {
let len = arr.len() as isize;
let actual = if *idx < 0 { len + *idx } else { *idx };
if actual >= 0 && (actual as usize) < arr.len() {
let v = &mut arr[actual as usize];
if tail.is_empty() {
f(v);
count += 1;
} else {
count += mutate_recursive(v, tail, f);
}
}
}
}
PathSegment::Wildcard => {
if let Some(obj) = node.as_object_mut() {
for (_, child) in obj.iter_mut() {
if tail.is_empty() {
f(child);
count += 1;
} else {
count += mutate_recursive(child, tail, f);
}
}
} else if let Some(arr) = node.as_array_mut() {
for child in arr.iter_mut() {
if tail.is_empty() {
f(child);
count += 1;
} else {
count += mutate_recursive(child, tail, f);
}
}
}
}
PathSegment::Filter(filter) => {
if let Some(arr) = node.as_array_mut() {
for item in arr.iter_mut() {
if eval_filter_expr(item, filter) {
if tail.is_empty() {
f(item);
count += 1;
} else {
count += mutate_recursive(item, tail, f);
}
}
}
}
}
_ => {}
}
if let Some(obj) = node.as_object_mut() {
for (_, child) in obj.iter_mut() {
count += mutate_recursive_descent(child, target, tail, f);
}
} else if let Some(arr) = node.as_array_mut() {
for child in arr.iter_mut() {
count += mutate_recursive_descent(child, target, tail, f);
}
}
count
}
pub fn delete_path_values(root: &mut Value, path: &str) -> Result<usize> {
let segments = parse_json_path(path)?;
if segments.is_empty() || (segments.len() == 1 && segments[0] == PathSegment::Root) {
return Ok(1);
}
let rest = if segments[0] == PathSegment::Root {
&segments[1..]
} else {
&segments[0..]
};
Ok(delete_recursive(root, rest))
}
fn delete_recursive(node: &mut Value, segments: &[PathSegment]) -> usize {
if segments.is_empty() {
return 0;
}
let head = &segments[0];
let tail = &segments[1..];
if tail.is_empty() {
match head {
PathSegment::Field(name) => {
if let Some(obj) = node.as_object_mut()
&& obj.remove(name).is_some()
{
return 1;
}
0
}
PathSegment::MultiField(names) => {
let mut count = 0;
if let Some(obj) = node.as_object_mut() {
for name in names {
if obj.remove(name).is_some() {
count += 1;
}
}
}
count
}
PathSegment::Index(idx) => {
if let Some(arr) = node.as_array_mut() {
let actual_idx = if *idx < 0 {
arr.len() as isize + *idx
} else {
*idx
};
if actual_idx >= 0 && (actual_idx as usize) < arr.len() {
arr.remove(actual_idx as usize);
return 1;
}
}
0
}
PathSegment::MultiIndex(slices) => {
let mut count = 0;
if let Some(arr) = node.as_array_mut() {
let mut indices = Vec::new();
let len = arr.len() as isize;
for item in slices {
match item {
SliceIndex::Index(idx) => {
let actual = if *idx < 0 { len + *idx } else { *idx };
if actual >= 0 && actual < len {
indices.push(actual as usize);
}
}
SliceIndex::Slice { start, stop, step } => {
let step_val = step.unwrap_or(1);
if step_val > 0 {
let s = start.unwrap_or(0).max(0);
let e = stop.unwrap_or(len).min(len);
let mut i = s;
while i < e {
indices.push(i as usize);
i += step_val;
}
}
}
}
}
indices.sort_unstable();
indices.dedup();
for idx in indices.into_iter().rev() {
if idx < arr.len() {
arr.remove(idx);
count += 1;
}
}
}
count
}
PathSegment::Wildcard => {
if let Some(obj) = node.as_object_mut() {
let len = obj.len();
obj.clear();
len
} else if let Some(arr) = node.as_array_mut() {
let len = arr.len();
arr.clear();
len
} else {
0
}
}
PathSegment::Filter(filter) => {
let mut count = 0;
if let Some(arr) = node.as_array_mut() {
let mut to_remove = Vec::new();
for (i, item) in arr.iter().enumerate() {
if eval_filter_expr(item, filter) {
to_remove.push(i);
}
}
for idx in to_remove.into_iter().rev() {
arr.remove(idx);
count += 1;
}
}
count
}
PathSegment::Recursive(inner) => delete_recursive_descent(node, inner, &[]),
_ => 0,
}
} else {
match head {
PathSegment::Field(name) => {
if let Some(obj) = node.as_object_mut()
&& let Some(child) = obj.get_mut(name)
{
delete_recursive(child, tail)
} else {
0
}
}
PathSegment::Index(idx) => {
if let Some(arr) = node.as_array_mut() {
let actual = if *idx < 0 {
arr.len() as isize + *idx
} else {
*idx
};
if actual >= 0 && (actual as usize) < arr.len() {
delete_recursive(&mut arr[actual as usize], tail)
} else {
0
}
} else {
0
}
}
PathSegment::Wildcard => {
let mut count = 0;
if let Some(obj) = node.as_object_mut() {
for (_, child) in obj.iter_mut() {
count += delete_recursive(child, tail);
}
} else if let Some(arr) = node.as_array_mut() {
for child in arr.iter_mut() {
count += delete_recursive(child, tail);
}
}
count
}
PathSegment::Recursive(inner) => delete_recursive_descent(node, inner, tail),
_ => 0,
}
}
}
fn delete_recursive_descent(node: &mut Value, target: &PathSegment, tail: &[PathSegment]) -> usize {
let mut count = 0;
if tail.is_empty() {
match target {
PathSegment::Field(name) => {
if let Some(obj) = node.as_object_mut()
&& obj.remove(name).is_some()
{
count += 1;
}
}
PathSegment::Index(idx) => {
if let Some(arr) = node.as_array_mut() {
let len = arr.len() as isize;
let actual = if *idx < 0 { len + *idx } else { *idx };
if actual >= 0 && (actual as usize) < arr.len() {
arr.remove(actual as usize);
count += 1;
}
}
}
PathSegment::Wildcard => {
if let Some(obj) = node.as_object_mut() {
count += obj.len();
obj.clear();
} else if let Some(arr) = node.as_array_mut() {
count += arr.len();
arr.clear();
}
}
PathSegment::Filter(filter) => {
if let Some(arr) = node.as_array_mut() {
let mut to_remove = Vec::new();
for (i, item) in arr.iter().enumerate() {
if eval_filter_expr(item, filter) {
to_remove.push(i);
}
}
for i in to_remove.into_iter().rev() {
arr.remove(i);
count += 1;
}
}
}
_ => {}
}
} else {
match target {
PathSegment::Field(name) => {
if let Some(obj) = node.as_object_mut()
&& let Some(child) = obj.get_mut(name)
{
count += delete_recursive(child, tail);
}
}
PathSegment::Index(idx) => {
if let Some(arr) = node.as_array_mut() {
let len = arr.len() as isize;
let actual = if *idx < 0 { len + *idx } else { *idx };
if actual >= 0 && (actual as usize) < arr.len() {
count += delete_recursive(&mut arr[actual as usize], tail);
}
}
}
PathSegment::Wildcard => {
if let Some(obj) = node.as_object_mut() {
for (_, child) in obj.iter_mut() {
count += delete_recursive(child, tail);
}
} else if let Some(arr) = node.as_array_mut() {
for child in arr.iter_mut() {
count += delete_recursive(child, tail);
}
}
}
PathSegment::Filter(filter) => {
if let Some(arr) = node.as_array_mut() {
for item in arr.iter_mut() {
if eval_filter_expr(item, filter) {
count += delete_recursive(item, tail);
}
}
}
}
_ => {}
}
}
if let Some(obj) = node.as_object_mut() {
for (_, child) in obj.iter_mut() {
count += delete_recursive_descent(child, target, tail);
}
} else if let Some(arr) = node.as_array_mut() {
for child in arr.iter_mut() {
count += delete_recursive_descent(child, target, tail);
}
}
count
}
pub fn json_merge_patch(target: &mut Value, patch: &Value) {
if let Some(patch_obj) = patch.as_object() {
if !target.is_object() {
*target = sonic_rs::json!({});
}
let target_obj = target.as_object_mut().unwrap();
for (k, v) in patch_obj.iter() {
if v.is_null() {
target_obj.remove(&k);
} else {
let entry = target_obj.entry(k).or_insert(sonic_rs::json!(null));
json_merge_patch(entry, v);
}
}
} else {
*target = patch.clone();
}
}
pub fn format_json(
val: &Value,
indent: Option<&str>,
newline: Option<&str>,
space: Option<&str>,
) -> String {
if indent.is_none() && newline.is_none() && space.is_none() {
return sonic_rs::to_string(val).unwrap_or_default();
}
let mut out = String::with_capacity(128);
format_value_recursive(val, 0, indent, newline, space, &mut out);
out
}
fn format_value_recursive(
val: &Value,
depth: usize,
indent: Option<&str>,
newline: Option<&str>,
space: Option<&str>,
out: &mut String,
) {
use std::fmt::Write as _;
if val.is_null() {
out.push_str("null");
} else if let Some(b) = val.as_bool() {
out.push_str(if b { "true" } else { "false" });
} else if let Some(i) = val.as_i64() {
let _ = write!(out, "{i}");
} else if let Some(u) = val.as_u64() {
let _ = write!(out, "{u}");
} else if let Some(f) = val.as_f64() {
let _ = write!(out, "{f}");
} else if val.is_str() {
out.push_str(&sonic_rs::to_string(val).unwrap_or_default());
} else if let Some(arr) = val.as_array() {
if arr.is_empty() {
out.push_str("[]");
return;
}
let nl = newline.unwrap_or(if indent.is_some() { "\n" } else { "" });
let ind = indent.unwrap_or("");
let has_nl = !nl.is_empty();
out.push('[');
for (i, elem) in arr.iter().enumerate() {
if i > 0 {
out.push(',');
if space.is_some() && !has_nl {
out.push(' ');
}
}
if has_nl {
out.push_str(nl);
for _ in 0..=depth {
out.push_str(ind);
}
}
format_value_recursive(elem, depth + 1, indent, newline, space, out);
}
if has_nl {
out.push_str(nl);
for _ in 0..depth {
out.push_str(ind);
}
}
out.push(']');
} else if let Some(obj) = val.as_object() {
if obj.is_empty() {
out.push_str("{}");
return;
}
let nl = newline.unwrap_or(if indent.is_some() { "\n" } else { "" });
let ind = indent.unwrap_or("");
let has_nl = !nl.is_empty();
let colon_sep = if space.is_some() || indent.is_some() {
": "
} else {
":"
};
out.push('{');
for (i, (k, v)) in obj.iter().enumerate() {
if i > 0 {
out.push(',');
if space.is_some() && !has_nl {
out.push(' ');
}
}
if has_nl {
out.push_str(nl);
for _ in 0..=depth {
out.push_str(ind);
}
}
out.push('"');
out.push_str(k);
out.push('"');
out.push_str(colon_sep);
format_value_recursive(v, depth + 1, indent, newline, space, out);
}
if has_nl {
out.push_str(nl);
for _ in 0..depth {
out.push_str(ind);
}
}
out.push('}');
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_jsonpath_slicing() {
let data: Value = sonic_rs::from_str(r#"[0, 1, 2, 3, 4, 5]"#).unwrap();
let s1 = get_path_values(&data, "$[1:4]").unwrap();
assert_eq!(s1.len(), 3);
assert_eq!(s1[0].as_i64(), Some(1));
assert_eq!(s1[1].as_i64(), Some(2));
assert_eq!(s1[2].as_i64(), Some(3));
let s2 = get_path_values(&data, "$[::-1]").unwrap();
assert_eq!(s2.len(), 6);
assert_eq!(s2[0].as_i64(), Some(5));
assert_eq!(s2[5].as_i64(), Some(0));
let s3 = get_path_values(&data, "$[::2]").unwrap();
assert_eq!(s3.len(), 3);
assert_eq!(s3[0].as_i64(), Some(0));
assert_eq!(s3[1].as_i64(), Some(2));
assert_eq!(s3[2].as_i64(), Some(4));
}
#[test]
fn test_jsonpath_filter() {
let data: Value = sonic_rs::from_str(
r#"[
{"name": "Alice", "age": 25, "active": true},
{"name": "Bob", "age": 17, "active": false},
{"name": "Charlie", "age": 30, "active": true}
]"#,
)
.unwrap();
let adults = get_path_values(&data, "$[?(@.age >= 18)]").unwrap();
assert_eq!(adults.len(), 2);
assert_eq!(adults[0]["name"].as_str(), Some("Alice"));
assert_eq!(adults[1]["name"].as_str(), Some("Charlie"));
let active_users = get_path_values(&data, "$[?(@.active)]").unwrap();
assert_eq!(active_users.len(), 2);
let bob = get_path_values(&data, r#"$[?(@.name == 'Bob')]"#).unwrap();
assert_eq!(bob.len(), 1);
assert_eq!(bob[0]["age"].as_i64(), Some(17));
}
#[test]
fn test_jsonpath_mutation_and_deletion() {
let mut data: Value = sonic_rs::from_str(
r#"{"store": {"book": [{"title": "Rust", "price": 40}, {"title": "Go", "price": 30}]}}"#,
)
.unwrap();
let mut_count = mutate_path_values(&mut data, "$..price", |p| {
if let Some(f) = p.as_f64() {
*p = sonic_rs::json!((f + 5.0) as i64);
}
})
.unwrap();
assert_eq!(mut_count, 2);
let prices = get_path_values(&data, "$..price").unwrap();
assert_eq!(prices[0].as_i64(), Some(45));
assert_eq!(prices[1].as_i64(), Some(35));
let del_count = delete_path_values(&mut data, "$.store.book[0]").unwrap();
assert_eq!(del_count, 1);
let books = get_path_values(&data, "$.store.book[*]").unwrap();
assert_eq!(books.len(), 1);
assert_eq!(books[0]["title"].as_str(), Some("Go"));
}
#[test]
fn test_format_json_options() {
let val: Value = sonic_rs::from_str(r#"{"a": 1, "b": [2, 3]}"#).unwrap();
let compact = format_json(&val, None, None, None);
assert_eq!(compact, r#"{"a":1,"b":[2,3]}"#);
let spaced = format_json(&val, None, None, Some(" "));
assert_eq!(spaced, r#"{"a": 1, "b": [2, 3]}"#);
let pretty = format_json(&val, Some(" "), Some("\n"), Some(" "));
assert!(pretty.contains(" \"a\": 1"));
assert!(pretty.contains(" \"b\": ["));
}
}