use serde_json::Value;
#[derive(Debug, Clone)]
pub enum Segment {
Key(String),
Slice { start: usize, end: usize },
}
pub type FilterPath = Vec<Segment>;
pub fn parse(expression: &str) -> Vec<FilterPath> {
let tokens = split_top_level_commas(expression);
tokens.iter().map(|t| parse_token(t)).collect()
}
pub fn apply(data: &Value, paths: &[FilterPath]) -> Value {
if paths.is_empty() {
return data.clone();
}
if paths.len() == 1 && paths[0].len() == 1 {
if let Segment::Key(key) = &paths[0][0] {
if let Value::Array(arr) = data {
return Value::Array(arr.iter().map(|item| apply(item, paths)).collect());
}
if let Value::Object(obj) = data {
if let Some(val) = obj.get(key) {
if is_scalar(val) {
return val.clone();
}
let mut result = serde_json::Map::new();
result.insert(key.clone(), val.clone());
return Value::Object(result);
}
}
return Value::Null;
}
}
if let Value::Array(arr) = data {
return Value::Array(arr.iter().map(|item| apply(item, paths)).collect());
}
let mut result = serde_json::Map::new();
for path in paths {
merge(&mut result, data, path, 0);
}
Value::Object(result)
}
fn is_scalar(value: &Value) -> bool {
matches!(
value,
Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_)
)
}
fn split_top_level_commas(expression: &str) -> Vec<String> {
let mut tokens = Vec::new();
let mut current = String::new();
let mut depth: i32 = 0;
for ch in expression.chars() {
match ch {
'[' => {
depth += 1;
current.push(ch);
}
']' => {
depth -= 1;
current.push(ch);
}
',' if depth == 0 => {
if !current.is_empty() {
tokens.push(current);
}
current = String::new();
}
_ => {
current.push(ch);
}
}
}
if !current.is_empty() {
tokens.push(current);
}
tokens
}
fn parse_token(token: &str) -> FilterPath {
let mut path = Vec::new();
let mut remaining = token;
while !remaining.is_empty() {
if let Some(bracket_idx) = remaining.find('[') {
let before = &remaining[..bracket_idx];
for part in before.split('.') {
if !part.is_empty() {
path.push(Segment::Key(part.to_string()));
}
}
let close_bracket = remaining[bracket_idx..]
.find(']')
.map(|i| bracket_idx + i)
.unwrap_or(remaining.len());
let inner = &remaining[bracket_idx + 1..close_bracket];
let parts: Vec<&str> = inner.split(',').collect();
if parts.len() == 2 {
let start = parts[0].parse::<usize>().unwrap_or(0);
let end = parts[1].parse::<usize>().unwrap_or(0);
path.push(Segment::Slice { start, end });
} else if parts.len() == 1 {
let idx = parts[0].parse::<usize>().unwrap_or(0);
path.push(Segment::Slice {
start: idx,
end: idx + 1,
});
}
remaining = if close_bracket + 1 < remaining.len() {
let rest = &remaining[close_bracket + 1..];
if rest.starts_with('.') {
&rest[1..]
} else {
rest
}
} else {
""
};
} else {
for part in remaining.split('.') {
if !part.is_empty() {
path.push(Segment::Key(part.to_string()));
}
}
break;
}
}
path
}
fn merge(
target: &mut serde_json::Map<String, Value>,
data: &Value,
segments: &[Segment],
index: usize,
) {
if index >= segments.len() {
return;
}
let obj = match data {
Value::Object(obj) => obj,
_ => return,
};
match &segments[index] {
Segment::Key(key) => {
let val = match obj.get(key) {
Some(v) => v,
None => return,
};
if index + 1 >= segments.len() {
target.insert(key.clone(), val.clone());
return;
}
let next = &segments[index + 1];
if let Segment::Slice { start, end } = next {
if let Value::Array(arr) = val {
let sliced: Vec<Value> = arr
.iter()
.skip(*start)
.take(end.saturating_sub(*start))
.cloned()
.collect();
if index + 2 >= segments.len() {
target.insert(key.clone(), Value::Array(sliced));
} else {
let mapped: Vec<Value> = sliced
.iter()
.map(|item| {
let mut sub = serde_json::Map::new();
merge(&mut sub, item, segments, index + 2);
Value::Object(sub)
})
.collect();
target.insert(key.clone(), Value::Array(mapped));
}
}
return;
}
if let Value::Object(_) = val {
let nested = target
.entry(key.clone())
.or_insert_with(|| Value::Object(serde_json::Map::new()));
if let Value::Object(nested_map) = nested {
merge(nested_map, val, segments, index + 1);
}
}
}
Segment::Slice { .. } => {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_parse_single_key() {
let paths = parse("foo");
assert_eq!(paths.len(), 1);
assert_eq!(paths[0].len(), 1);
assert!(matches!(&paths[0][0], Segment::Key(k) if k == "foo"));
}
#[test]
fn test_parse_multiple_keys() {
let paths = parse("foo,bar,baz");
assert_eq!(paths.len(), 3);
}
#[test]
fn test_parse_dotted_path() {
let paths = parse("foo.bar.baz");
assert_eq!(paths.len(), 1);
assert_eq!(paths[0].len(), 3);
assert!(matches!(&paths[0][0], Segment::Key(k) if k == "foo"));
assert!(matches!(&paths[0][1], Segment::Key(k) if k == "bar"));
assert!(matches!(&paths[0][2], Segment::Key(k) if k == "baz"));
}
#[test]
fn test_parse_with_slice() {
let paths = parse("items[0,3]");
assert_eq!(paths.len(), 1);
assert_eq!(paths[0].len(), 2);
assert!(matches!(&paths[0][0], Segment::Key(k) if k == "items"));
assert!(matches!(&paths[0][1], Segment::Slice { start: 0, end: 3 }));
}
#[test]
fn test_parse_complex_expression() {
let paths = parse("foo,bar.baz,a[0,3]");
assert_eq!(paths.len(), 3);
assert_eq!(paths[0].len(), 1);
assert!(matches!(&paths[0][0], Segment::Key(k) if k == "foo"));
assert_eq!(paths[1].len(), 2);
assert!(matches!(&paths[1][0], Segment::Key(k) if k == "bar"));
assert!(matches!(&paths[1][1], Segment::Key(k) if k == "baz"));
assert_eq!(paths[2].len(), 2);
assert!(matches!(&paths[2][0], Segment::Key(k) if k == "a"));
assert!(matches!(&paths[2][1], Segment::Slice { start: 0, end: 3 }));
}
#[test]
fn test_parse_slice_then_key() {
let paths = parse("items[0,2].name");
assert_eq!(paths.len(), 1);
assert_eq!(paths[0].len(), 3);
assert!(matches!(&paths[0][0], Segment::Key(k) if k == "items"));
assert!(matches!(&paths[0][1], Segment::Slice { start: 0, end: 2 }));
assert!(matches!(&paths[0][2], Segment::Key(k) if k == "name"));
}
#[test]
fn test_apply_empty_paths() {
let data = json!({"a": 1, "b": 2});
let result = apply(&data, &[]);
assert_eq!(result, data);
}
#[test]
fn test_apply_single_scalar_key() {
let data = json!({"name": "alice", "age": 30});
let paths = parse("name");
let result = apply(&data, &paths);
assert_eq!(result, json!("alice"));
}
#[test]
fn test_apply_single_object_key() {
let data = json!({"user": {"name": "alice"}, "count": 1});
let paths = parse("user");
let result = apply(&data, &paths);
assert_eq!(result, json!({"user": {"name": "alice"}}));
}
#[test]
fn test_apply_multiple_keys() {
let data = json!({"a": 1, "b": 2, "c": 3});
let paths = parse("a,c");
let result = apply(&data, &paths);
assert_eq!(result, json!({"a": 1, "c": 3}));
}
#[test]
fn test_apply_nested_key() {
let data = json!({"user": {"name": "alice", "age": 30}});
let paths = parse("user.name");
let result = apply(&data, &paths);
assert_eq!(result, json!({"user": {"name": "alice"}}));
}
#[test]
fn test_apply_array_slice() {
let data = json!({"items": [1, 2, 3, 4, 5]});
let paths = parse("items[0,3]");
let result = apply(&data, &paths);
assert_eq!(result, json!({"items": [1, 2, 3]}));
}
#[test]
fn test_apply_slice_then_key() {
let data = json!({
"users": [
{"name": "alice", "age": 30},
{"name": "bob", "age": 25},
{"name": "charlie", "age": 35}
]
});
let paths = parse("users[0,2].name");
let result = apply(&data, &paths);
assert_eq!(
result,
json!({"users": [{"name": "alice"}, {"name": "bob"}]})
);
}
#[test]
fn test_apply_to_array_data() {
let data = json!([
{"name": "alice", "age": 30},
{"name": "bob", "age": 25}
]);
let paths = parse("name");
let result = apply(&data, &paths);
assert_eq!(result, json!(["alice", "bob"]));
}
#[test]
fn test_apply_missing_key() {
let data = json!({"a": 1});
let paths = parse("b");
let result = apply(&data, &paths);
assert_eq!(result, Value::Null);
}
#[test]
fn test_apply_multiple_paths_with_nesting() {
let data = json!({
"user": {"name": "alice", "email": "alice@example.com"},
"count": 42
});
let paths = parse("user.name,count");
let result = apply(&data, &paths);
assert_eq!(result, json!({"user": {"name": "alice"}, "count": 42}));
}
#[test]
fn test_parse_empty_string() {
let paths = parse("");
assert!(paths.is_empty());
}
#[test]
fn test_apply_to_array_multiple_keys() {
let data = json!([
{"name": "alice", "age": 30, "city": "NYC"},
{"name": "bob", "age": 25, "city": "LA"}
]);
let paths = parse("name,age");
let result = apply(&data, &paths);
assert_eq!(
result,
json!([
{"name": "alice", "age": 30},
{"name": "bob", "age": 25}
])
);
}
}