csonpath 0.19.0

Partial JSONPath implementation in C with Rust bindings
use std::time::Instant;
use csonpath::CsonPath;
use regex::Regex;
use serde_json::json;
use serde_json::Value;

fn rec_get_title(val: &Value, out: &mut Value) {
    match val {
        Value::Object(map) => {
            for (k, v) in map {
                if k == "title" {
                    if let Value::Array(arr) = out {
                        arr.push(v.clone());
                    }
                }
                rec_get_title(v, out);
            }
        }
        Value::Array(inner) => {
            for v in inner {
                rec_get_title(v, out);
            }
        }
        _ => {}
    }
}

fn build_book(i: usize) -> Value {
    json!({
        "category": "fiction",
        "title": format!("Book {}", i),
        "price": (i % 50) + 5,
        "deep_obj": {
            "a": {
                "b": {
                    "c": {
                        "d": {
                            "e": {
                                "f": {
                                    "g": {
                                        "h": {
                                            "i": {
                                                "j": {
                                                    "k": {
                                                        "l": 42
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        },
        "deep_mixed": {
            "a": {
                "b": {
                    "c": [
                        {
                            "d": {
                                "e": {
                                    "f": [
                                        {
                                            "g": {
                                                "h": {
                                                    "i": [
                                                        {
                                                            "j": {
                                                                "k": {
                                                                    "l": 42
                                                                }
                                                            }
                                                        }
                                                    ]
                                                }
                                            }
                                        }
                                    ]
                                }
                            }
                        }
                    ]
                }
            }
        }
    })
}

fn bench_csonpath(cp: &CsonPath, data: &Value, iters: usize) -> (Option<Value>, f64) {
    let t0 = Instant::now();
    let mut result = None;
    for _ in 0..iters {
        result = cp.find_all(data).unwrap();
    }
    (result, t0.elapsed().as_secs_f64())
}

fn bench_pure_rust(data: &Value, label: &str, iters: usize) -> (Value, f64) {
    let t0 = Instant::now();
    let mut result = Value::Null;
    for _ in 0..iters {
        result = match label {
            "all_titles" => {
                data["store"]["book"]
                    .as_array()
                    .unwrap()
                    .iter()
                    .map(|b| b["title"].clone())
                    .collect::<Value>()
            }
            "filter_gt_20" => {
                data["store"]["book"]
                    .as_array()
                    .unwrap()
                    .iter()
                    .filter(|b| b["price"].as_i64().unwrap_or(0) > 20)
                    .map(|b| b["title"].clone())
                    .collect::<Value>()
            }
            "union_title_category" => {
                data["store"]["book"]
                    .as_array()
                    .unwrap()
                    .iter()
                    .flat_map(|b| [b["title"].clone(), b["category"].clone()])
                    .collect::<Value>()
            }
            "recursive_title" => {
                let mut out = json!([]);
                rec_get_title(&data, &mut out);
                out
            }
            "deep_obj" => {
                data["store"]["book"]
                    .as_array()
                    .unwrap()
                    .iter()
                    .map(|book| {
                        book["deep_obj"]["a"]["b"]["c"]["d"]["e"]["f"]["g"]["h"]["i"]["j"]["k"]["l"].clone()
                    })
                    .collect::<Value>()
            }
            "deep_mixed" => {
                data["store"]["book"]
                    .as_array()
                    .unwrap()
                    .iter()
                    .map(|book| {
                        book["deep_mixed"]["a"]["b"]["c"][0]["d"]["e"]["f"][0]["g"]["h"]["i"][0]["j"]["k"]["l"].clone()
                    })
                    .collect::<Value>()
            }
            "regex_book" => {
                let re = Regex::new("Book").unwrap();
                data["store"]["book"]
                    .as_array()
                    .unwrap()
                    .iter()
                    .filter(|b| b["title"].as_str().map(|s| re.is_match(s)).unwrap_or(false))
                    .map(|b| b["title"].clone())
                    .collect::<Value>()
            }
            _ => Value::Null,
        };
    }
    (result, t0.elapsed().as_secs_f64())
}

fn result_len(r: &Value) -> usize {
    r.as_array().map(|a| a.len()).unwrap_or(0)
}

fn main() {
    let csv_mode = std::env::var("BENCH_CSV").is_ok();
    let csv_header = std::env::var("BENCH_CSV_HEADER").is_ok();

    let data = json!({"a": "hello"});
    let cp = CsonPath::new("$.a").unwrap();
    let r = cp.find_first(&data).unwrap();
    assert_eq!(r, Some(json!("hello")));

    let n = 5000;

    let books: Value = (0..n)
        .map(build_book)
        .collect::<Value>();

    let data = json!({
        "store": {
            "book": books,
            "bicycle": { "color": "red", "price": 19.95 }
        }
    });

    let queries = vec![
        ("$.store.book[*].title", "all_titles"),
        ("$.store.book[?(@.price > 20)].title", "filter_gt_20"),
        ("$.store.book[*]['title','category']", "union_title_category"),
        ("$..title", "recursive_title"),
        ("$.store.book[*].deep_obj.a.b.c.d.e.f.g.h.i.j.k.l", "deep_obj"),
        ("$.store.book[*].deep_mixed.a.b.c[0].d.e.f[0].g.h.i[0].j.k.l", "deep_mixed"),
        ("$.store.book[?title =~ /Book/].title", "regex_book"),
    ];

    let iters = 100;
    let scale = 10.0;

    if csv_header {
        println!("category,query,results,time");
    }

    let mut total_csonpath = 0.0;
    let mut total_pure = 0.0;
    let mut csv_csonpath: Vec<(String, usize, f64)> = Vec::new();
    let mut csv_pure: Vec<(String, usize, f64)> = Vec::new();

    for (query, label) in queries {
        let escaped = query.replace("\"", "\"\"");
        if !csv_mode {
            println!("=== {} ===", query);
        }

        let cp = CsonPath::new(query).unwrap();
        let (r, t) = bench_csonpath(&cp, &data, iters);
        let t_scaled = t * scale;
        total_csonpath += t_scaled;
        if csv_mode {
            csv_csonpath.push((escaped.clone(), result_len(r.as_ref().unwrap_or(&Value::Null)), t_scaled));
        } else {
            println!("csonpath rust : {} results, in {} loop, Time: {} seconds",
                     result_len(r.as_ref().unwrap_or(&Value::Null)), iters, t);
        }

        let (r_pure, t_pure) = bench_pure_rust(&data, label, iters);
        let t_pure_scaled = t_pure * scale;
        total_pure += t_pure_scaled;
        if csv_mode {
            csv_pure.push((escaped, result_len(&r_pure), t_pure_scaled));
        } else {
            println!("pure rust     : {} results, in {} loop, Time: {} seconds",
                     result_len(&r_pure), iters, t_pure);
        }

        if !csv_mode {
            let ratio = if t > 0.0 { t_pure / t } else { 0.0 };
            println!("speedup       : {:.2}x\n", ratio);
        }
    }

    if csv_mode {
        for (q, r, t) in csv_csonpath {
            println!("csonpath-rust,\"{}\",{},{:.6}", q, r, t);
        }
        println!("csonpath-rust,TOTAL,0,{:.6}", total_csonpath);

        for (q, r, t) in csv_pure {
            println!("pure-rust,\"{}\",{},{:.6}", q, r, t);
        }
        println!("pure-rust,TOTAL,0,{:.6}", total_pure);
    }
}