Skip to main content

qualia_core_db/sparql_library/serialisers/
json_serializer.rs

1//! JSON Serializer for QualiaDB
2//!
3//! Serializes NQuin data to JSON format with proper type handling.
4
5use std::io::Write;
6
7use crate::NQuin;
8
9/// JSON serialization configuration
10#[derive(Debug, Clone)]
11pub struct JsonSerializationProfile {
12    pub field_names: Vec<String>,
13    pub predicate_hashes: Vec<u64>,
14    pub datatypes: Vec<JsonDatatype>,
15}
16
17/// Data type for JSON serialization
18#[derive(Debug, Clone, Copy)]
19pub enum JsonDatatype {
20    Integer,
21    Float,
22    DateTime,
23    StringRef,
24}
25
26/// Serialize Quins to JSON format
27pub fn serialize_quins_to_json<W: Write>(
28    writer: &mut W,
29    quins: &[NQuin],
30    profile: &JsonSerializationProfile,
31) -> Result<(), String> {
32    writeln!(writer, "[").map_err(|e| format!("Failed to write JSON array start: {}", e))?;
33
34    // Group quins by subject (assuming each subject represents an object)
35    let mut subjects: std::collections::HashMap<u64, Vec<&NQuin>> =
36        std::collections::HashMap::new();
37    for quin in quins {
38        subjects
39            .entry(quin.subject)
40            .or_insert_with(Vec::new)
41            .push(quin);
42    }
43
44    let mut first = true;
45    for (_subject, row_quins) in subjects {
46        if !first {
47            writeln!(writer, ",").map_err(|e| format!("Failed to write JSON comma: {}", e))?;
48        }
49        first = false;
50
51        write!(writer, "  {{").map_err(|e| format!("Failed to write JSON object start: {}", e))?;
52
53        let mut field_first = true;
54        for (i, &pred_hash) in profile.predicate_hashes.iter().enumerate() {
55            let field_name = &profile.field_names[i];
56            let datatype = profile
57                .datatypes
58                .get(i)
59                .copied()
60                .unwrap_or(JsonDatatype::StringRef);
61
62            // Find quin with matching predicate
63            if let Some(quin) = row_quins.iter().find(|q| q.predicate == pred_hash) {
64                if !field_first {
65                    write!(writer, ",")
66                        .map_err(|e| format!("Failed to write JSON field comma: {}", e))?;
67                }
68                field_first = false;
69
70                write!(
71                    writer,
72                    "\"{}\": {}",
73                    field_name,
74                    format_quin_value(quin, datatype)
75                )
76                .map_err(|e| format!("Failed to write JSON field: {}", e))?;
77            }
78        }
79
80        write!(writer, "}}").map_err(|e| format!("Failed to write JSON object end: {}", e))?;
81    }
82
83    writeln!(writer, "\n]").map_err(|e| format!("Failed to write JSON array end: {}", e))?;
84
85    Ok(())
86}
87
88/// Format a Quin value for JSON output
89fn format_quin_value(quin: &NQuin, datatype: JsonDatatype) -> String {
90    match datatype {
91        JsonDatatype::Integer => {
92            let val = quin.object & 0x0FFF_FFFF_FFFF_FFFF; // Remove type tag
93            val.to_string()
94        }
95        JsonDatatype::Float => {
96            let tag = quin.object & 0xF000_0000_0000_0000;
97            if tag == (0b010 << 60) {
98                let bits = (quin.object & 0xFFFF_FFFF) as u32;
99                let float_val = f32::from_bits(bits);
100                float_val.to_string()
101            } else {
102                "null".to_string()
103            }
104        }
105        JsonDatatype::DateTime => {
106            let tag = quin.object & 0xF000_0000_0000_0000;
107            if tag == (0b011 << 60) {
108                let millis = quin.object & 0x0FFF_FFFF_FFFF_FFFF;
109                let dt = chrono::DateTime::from_timestamp(millis as i64 / 1000, 0);
110                let dt_str = dt.map(|d| d.to_rfc3339()).unwrap_or_default();
111                format!("\"{}\"", dt_str)
112            } else {
113                "null".to_string()
114            }
115        }
116        JsonDatatype::StringRef => {
117            // For strings, we just output the hash (in practice, you'd want lexicon lookup)
118            format!("\"{:x}\"", quin.object)
119        }
120    }
121}