use serde_json::Value;
#[derive(Debug, Clone)]
pub enum PathElement {
Key(String),
Index(usize),
Wildcard, }
impl From<&str> for PathElement {
fn from(s: &str) -> Self {
match s {
"*" => PathElement::Wildcard,
_ => PathElement::Key(s.to_string()),
}
}
}
impl From<String> for PathElement {
fn from(s: String) -> Self {
match s.as_str() {
"*" => PathElement::Wildcard,
_ => PathElement::Key(s),
}
}
}
impl From<usize> for PathElement {
fn from(i: usize) -> Self {
PathElement::Index(i)
}
}
impl From<i32> for PathElement {
fn from(i: i32) -> Self {
PathElement::Index(i as usize)
}
}
#[macro_export]
macro_rules! path {
($($element:expr),*) => {
vec![$(PathElement::from($element)),*]
};
}
pub fn extract<T>(value: &Value, path: &[PathElement], default: T) -> T
where
T: Clone + for<'de> serde::Deserialize<'de>,
{
match extract_raw(value, path) {
Some(v) => serde_json::from_value(v.clone()).unwrap_or(default),
None => default,
}
}
pub fn extract_raw(value: &Value, path: &[PathElement]) -> Option<Value> {
let mut current = value;
for (i, element) in path.iter().enumerate() {
match element {
PathElement::Key(key) => {
current = current.get(key)?;
}
PathElement::Index(index) => {
current = current.get(*index)?;
}
PathElement::Wildcard => {
let remaining_path = &path[i + 1..];
return handle_wildcard(current, remaining_path);
}
}
}
Some(current.clone())
}
fn handle_wildcard(value: &Value, remaining_path: &[PathElement]) -> Option<Value> {
let mut results = Vec::new();
if let Some(arr) = value.as_array() {
for item in arr {
if let Some(val) = extract_raw(item, remaining_path) {
match val {
Value::Array(a) => results.extend(a),
_ => results.push(val),
}
}
}
}
else if let Some(obj) = value.as_object() {
for (_, val) in obj {
if let Some(v) = extract_raw(val, remaining_path) {
match v {
Value::Array(a) => results.extend(a),
_ => results.push(v),
}
}
}
}
Some(Value::Array(results))
}
pub trait JsonExtract {
fn get<T>(&self, path: &[PathElement], default: T) -> T
where
T: Clone + for<'de> serde::Deserialize<'de>;
fn get_raw(&self, path: &[PathElement]) -> Option<Value>;
}
impl JsonExtract for Value {
fn get<T>(&self, path: &[PathElement], default: T) -> T
where
T: Clone + for<'de> serde::Deserialize<'de>
{
extract(self, path, default)
}
fn get_raw(&self, path: &[PathElement]) -> Option<Value> {
extract_raw(self, path)
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_basic_paths() {
let data = json!({
"data": {
"items": [
{"name": "alice", "age": 20},
{"name": "bob", "age": 25}
],
"data.item": "带点号的key",
"count": 2
}
});
let name: String = extract(&data, &path!["data","items",0,"name"], "unknown".to_string());
assert_eq!(name, "alice");
let age: i32 = extract(&data, &path!["data","items",1,"age"], 0);
assert_eq!(age, 25);
let name2: String = extract(&data, &path!["data", "items", "0", "name"], "unknown".to_string());
assert_eq!(name2, "unknown");
let dot_key: String = extract(&data, &path!["data", "data.item"], "default".to_string());
assert_eq!(dot_key, "带点号的key");
}
#[test]
fn test_wildcards() {
let data = json!({
"users": [
{"name": "alice", "age": 20},
{"name": "bob", "age": 25},
{"name": "charlie", "age": 30}
],
"groups": {
"admin": {"name": "admin_user", "age": 35},
"user": {"name": "normal_user", "age": 28}
}
});
let ages: Vec<i32> = extract(&data, &path!["users","*","age"], vec![]);
assert_eq!(ages, vec![20, 25, 30]);
let group_names: Vec<String> = extract(&data, &path!["groups","*","name"], vec![]);
assert!(group_names.contains(&"admin_user".to_string()));
assert!(group_names.contains(&"normal_user".to_string()));
}
#[test]
fn test_macro() {
let data = json!({
"data": {
"items": [{"value": 42}]
}
});
let value: i32 = extract(&data, &path!["data", "items", 0, "value"], 0);
assert_eq!(value, 42);
}
#[test]
fn test_mixed_types() {
let data = json!({
"level1": {
"level2": [
{
"data.with.dots": "found it"
}
]
}
});
let result: String = extract(
&data,
&path!["level1", "level2", 0, "data.with.dots"],
"not found".to_string()
);
assert_eq!(result, "found it");
}
#[test]
fn test_extract_string_basic() {
let json = json!({
"name": "张三",
"age": 30,
"active": true
});
assert_eq!(extract(&json, &path!["name"], "".to_string()), "张三");
assert_eq!(extract(&json, &path!["missing"], "默认值".to_string()), "默认值");
}
#[test]
fn test_extract_string_nested() {
let json = json!({
"user": {
"profile": {
"name": "李四",
"email": "lisi@example.com"
}
}
});
assert_eq!(extract(&json, &path!["user","profile","name"], "".to_string()), "李四");
assert_eq!(extract(&json, &path!["user","profile","email"], "".to_string()), "lisi@example.com");
assert_eq!(extract(&json, &path!["user","profile","phone"], "无".to_string()), "无");
}
#[test]
fn test_extract_number_basic() {
let json = json!({
"age": 25,
"score": 95.5,
"count": 0
});
assert_eq!(extract(&json, &path!["age"], 0i32), 25);
assert_eq!(extract(&json, &path!["score"], 0.0f64), 95.5);
assert_eq!(extract(&json, &path!["count"], -1i32), 0);
assert_eq!(extract(&json, &path!["missing"], 100i32), 100);
}
#[test]
fn test_extract_bool_basic() {
let json = json!({
"active": true,
"deleted": false,
"enabled": null
});
assert_eq!(extract(&json, &path!["active"], false), true);
assert_eq!(extract(&json, &path!["deleted"], true), false);
assert_eq!(extract(&json, &path!["missing"], true), true);
assert_eq!(extract(&json, &path!["enabled"], false), false);
}
#[test]
fn test_array_access() {
let json = json!({
"users": [
{"name": "用户1", "age": 20},
{"name": "用户2", "age": 25},
{"name": "用户3", "age": 30}
],
"tags": ["rust", "json", "serde"]
});
assert_eq!(extract(&json, &path!["users",0,"name"], "".to_string()), "用户1");
assert_eq!(extract(&json, &path!["users",1,"age"], 0i32), 25);
assert_eq!(extract(&json, &path!["users",2,"name"], "".to_string()), "用户3");
assert_eq!(extract(&json, &path!["tags",0], "".to_string()), "rust");
assert_eq!(extract(&json, &path!["tags",2], "".to_string()), "serde");
}
#[test]
fn test_array_out_of_bounds() {
let json = json!({
"items": ["a", "b", "c"]
});
assert_eq!(extract(&json, &path!["items",5], "默认".to_string()), "默认");
assert_eq!(extract(&json, &path!["items",10], 999i32), 999);
}
#[test]
fn test_complex_nested_structure() {
let json = json!({
"company": {
"name": "科技公司",
"departments": [
{
"name": "研发部",
"employees": [
{
"name": "王五",
"position": "工程师",
"skills": ["Rust", "Python", "JavaScript"]
},
{
"name": "赵六",
"position": "架构师",
"skills": ["Go", "Docker", "Kubernetes"]
}
]
}
]
}
});
assert_eq!(
extract(&json, &path!["company","name"], "".to_string()),
"科技公司"
);
assert_eq!(
extract(&json, &path!["company","departments",0,"name"], "".to_string()),
"研发部"
);
assert_eq!(
extract(&json, &path!["company","departments",0,"employees",0,"name"], "".to_string()),
"王五"
);
assert_eq!(
extract(&json, &path!["company","departments",0,"employees",1,"position"], "".to_string()),
"架构师"
);
assert_eq!(
extract(&json, &path!["company","departments",0,"employees",0,"skills",0], "".to_string()),
"Rust"
);
}
#[test]
fn test_type_conversion_failures() {
let json = json!({
"string_value": "不是数字",
"number_value": 42,
"bool_string": "true",
"null_value": null
});
assert_eq!(extract(&json, &path!["string_value"], 999i32), 999);
assert_eq!(extract(&json, &path!["number_value"], "default".to_string()), "default");
assert_eq!(extract(&json, &path!["bool_string"], false), false);
assert_eq!(extract(&json, &path!["null_value"], "默认".to_string()), "默认");
}
#[test]
fn test_extract_raw_value() {
let json = json!({
"object": {
"nested": "value"
},
"array": [1, 2, 3],
"string": "text",
"number": 42,
"boolean": true,
"null": null
});
let obj = extract(&json, &path!["object"], json!({}));
assert_eq!(obj, json!({ "nested": "value" }));
let arr = extract(&json, &path!["array"], json!([]));
assert_eq!(arr, json!([1, 2, 3]));
let str_val = extract(&json, &path!["string"], "".to_string());
assert_eq!(str_val, "text");
let num_val = extract(&json, &path!["number"], 0i32);
assert_eq!(num_val, 42);
let null_type: Vec<Value> = extract(&json, &path!["null"], vec![]);
println!("null_type:{:?}", null_type);
}
#[test]
fn test_xx_deep() {
let json = json!(
{
"people": {
"group1": {
"member1": {
"age": {"30":30}
},
"member2": {
"age": ["25"]
}
},
"group2": {
"member3": {
"age": [40,4000]
},
"member4": {
"age": "STRING35"
},
"member5": {
"age": null
},
"member6": {
"age": false
}
}
}
}
);
let age: Vec<Value> = extract(&json, &path!["*","*","*","age"], vec![]);
println!("age:{:?}", age);
}
}