Skip to main content

sparrowdb_execution/
json.rs

1//! JSON serialization helpers for execution types.
2//!
3//! Shared by `sparrowdb-cli` (NDJSON serve mode) and `sparrowdb-node`
4//! (napi-rs bindings) to avoid duplicating the same conversion logic.
5
6use crate::types::{QueryResult, Value};
7
8/// Convert a scalar [`Value`] to a [`serde_json::Value`].
9///
10/// `NodeRef` and `EdgeRef` are serialized as tagged objects with string ids:
11/// `{"$type": "node", "id": "123"}` / `{"$type": "edge", "id": "456"}`.
12/// IDs are strings (not numbers) to preserve u64 precision across the JS boundary.
13///
14/// `Int64` values within `±(2^53-1)` are plain JSON numbers; values outside
15/// that range are serialized as decimal strings to avoid silent precision loss.
16pub fn value_to_json(v: &Value) -> serde_json::Value {
17    match v {
18        Value::Null => serde_json::Value::Null,
19        // JavaScript's Number can only represent integers exactly up to 2^53-1.
20        // Serialize Int64 values outside the safe range as strings so callers
21        // don't silently receive wrong values.
22        Value::Int64(i) => {
23            const MAX_SAFE: i64 = (1_i64 << 53) - 1;
24            const MIN_SAFE: i64 = -MAX_SAFE;
25            if *i >= MIN_SAFE && *i <= MAX_SAFE {
26                serde_json::json!(i)
27            } else {
28                serde_json::json!(i.to_string())
29            }
30        }
31        Value::Float64(f) => serde_json::json!(f),
32        Value::Bool(b) => serde_json::json!(b),
33        Value::String(s) => serde_json::json!(s),
34        // IDs are u64, which exceeds JavaScript's safe integer range (2^53-1).
35        // Serialize as strings so precision is preserved across the JSON boundary.
36        Value::NodeRef(n) => serde_json::json!({"$type": "node", "id": n.0.to_string()}),
37        Value::EdgeRef(e) => serde_json::json!({"$type": "edge", "id": e.0.to_string()}),
38        Value::List(items) => serde_json::Value::Array(items.iter().map(value_to_json).collect()),
39        Value::Map(entries) => {
40            let obj: serde_json::Map<String, serde_json::Value> = entries
41                .iter()
42                .map(|(k, v)| (k.clone(), value_to_json(v)))
43                .collect();
44            serde_json::Value::Object(obj)
45        }
46    }
47}
48
49/// Materialize a [`QueryResult`] into a JSON object:
50///
51/// ```json
52/// {
53///   "columns": ["n.name", "n.age"],
54///   "rows": [{"n.name": "Alice", "n.age": 30}, ...]
55/// }
56/// ```
57pub fn query_result_to_json(result: &QueryResult) -> serde_json::Value {
58    let rows: Vec<serde_json::Value> = result
59        .rows
60        .iter()
61        .map(|row| {
62            let obj: serde_json::Map<String, serde_json::Value> = result
63                .columns
64                .iter()
65                .zip(row.iter())
66                .map(|(col, val)| (col.clone(), value_to_json(val)))
67                .collect();
68            serde_json::Value::Object(obj)
69        })
70        .collect();
71
72    serde_json::json!({
73        "columns": result.columns,
74        "rows": rows,
75    })
76}