qualia_core_db/sparql_library/parsers/
json_parser.rs1use chrono::{DateTime, NaiveDate, NaiveDateTime, TimeZone, Utc};
7use serde_json::{Deserializer, Value};
8use std::io::Read;
9
10use crate::mini_parser::hash_token;
11use crate::NQuin;
12
13#[derive(Debug, Clone, Copy)]
15pub enum JsonDatatype {
16 Integer,
17 Float,
18 DateTime,
19 StringRef,
20}
21
22#[derive(Debug, Clone)]
24pub struct JsonFieldMapping {
25 pub source_key: String,
26 pub predicate_hash: u64,
27 pub datatype: JsonDatatype,
28}
29
30#[derive(Debug, Clone)]
32pub struct JsonMappingProfile {
33 pub base_class_hash: u64,
34 pub fields: Vec<JsonFieldMapping>,
35}
36
37pub fn parse_json_to_quins<R: Read>(
39 reader: R,
40 profile: &JsonMappingProfile,
41 mut on_quin: impl FnMut(NQuin),
42) -> Result<(), String> {
43 let stream = Deserializer::from_reader(reader).into_iter::<Value>();
44
45 let mut emit_object = |obj: &serde_json::Map<String, Value>| {
46 let subject_hash: u64 = rand::random(); for field in &profile.fields {
49 if let Some(val) = obj.get(&field.source_key) {
50 match field.datatype {
51 JsonDatatype::Integer => {
52 let parsed_int: u64 = val.as_u64().unwrap_or(0);
53 let quin = NQuin {
54 subject: subject_hash,
55 predicate: field.predicate_hash,
56 object: parsed_int | (0b001 << 60), context: 0,
58 metadata: 0,
59 parity: NQuin::calculate_parity(
60 subject_hash,
61 field.predicate_hash,
62 parsed_int | (0b001 << 60),
63 0,
64 0,
65 ),
66 };
67 on_quin(quin);
68 }
69 JsonDatatype::Float => {
70 let parsed_float: f32 = val.as_f64().unwrap_or(0.0) as f32;
71 let float_bits: u32 = parsed_float.to_bits();
72 let inline_tag: u64 = 0b010 << 60;
73 let packed_object = inline_tag | (float_bits as u64);
74
75 let quin = NQuin {
76 subject: subject_hash,
77 predicate: field.predicate_hash,
78 object: packed_object,
79 context: 0,
80 metadata: 0,
81 parity: NQuin::calculate_parity(
82 subject_hash,
83 field.predicate_hash,
84 packed_object,
85 0,
86 0,
87 ),
88 };
89 on_quin(quin);
90 }
91 JsonDatatype::StringRef => {
92 if let Some(s) = val.as_str() {
93 let hash = hash_token(s);
94 let quin = NQuin {
95 subject: subject_hash,
96 predicate: field.predicate_hash,
97 object: hash,
98 context: 0,
99 metadata: 0,
100 parity: NQuin::calculate_parity(
101 subject_hash,
102 field.predicate_hash,
103 hash,
104 0,
105 0,
106 ),
107 };
108 on_quin(quin);
109 }
110 }
111 JsonDatatype::DateTime => {
112 let s = match val.as_str() {
113 Some(s) => s,
114 None => continue,
115 };
116 let millis: u64 = parse_datetime_millis(s).unwrap_or(0);
117 let quin = NQuin {
118 subject: subject_hash,
119 predicate: field.predicate_hash,
120 object: (0b011u64 << 60) | millis,
121 context: 0,
122 metadata: 0,
123 parity: NQuin::calculate_parity(
124 subject_hash,
125 field.predicate_hash,
126 (0b011u64 << 60) | millis,
127 0,
128 0,
129 ),
130 };
131 on_quin(quin);
132 }
133 }
134 }
135 }
136 };
137
138 for value in stream {
139 match value {
140 Ok(Value::Object(obj)) => emit_object(&obj),
141 Ok(Value::Array(entries)) => {
142 for entry in entries {
143 if let Value::Object(obj) = entry {
144 emit_object(&obj);
145 }
146 }
147 }
148 _ => {}
149 }
150 }
151
152 Ok(())
153}
154
155fn parse_datetime_millis(s: &str) -> Option<u64> {
157 if let Ok(dt) = DateTime::parse_from_rfc3339(s) {
158 return Some(dt.timestamp_millis() as u64);
159 }
160 for fmt in &[
161 "%Y-%m-%dT%H:%M:%S",
162 "%Y-%m-%d %H:%M:%S",
163 "%Y-%m-%dT%H:%M:%SZ",
164 ] {
165 if let Ok(nd) = NaiveDateTime::parse_from_str(s, fmt) {
166 return Some(Utc.from_utc_datetime(&nd).timestamp_millis() as u64);
167 }
168 }
169 if let Ok(d) = NaiveDate::parse_from_str(s, "%Y-%m-%d") {
170 let nd = d.and_hms_opt(0, 0, 0)?;
171 return Some(Utc.from_utc_datetime(&nd).timestamp_millis() as u64);
172 }
173 None
174}
175
176#[cfg(test)]
177mod tests {
178 use super::{parse_json_to_quins, JsonDatatype, JsonFieldMapping, JsonMappingProfile};
179
180 #[test]
181 fn parses_each_object_in_a_top_level_array() {
182 let profile = JsonMappingProfile {
183 base_class_hash: 0,
184 fields: vec![JsonFieldMapping {
185 source_key: "name".to_owned(),
186 predicate_hash: 42,
187 datatype: JsonDatatype::StringRef,
188 }],
189 };
190 let mut quins = Vec::new();
191
192 parse_json_to_quins(
193 br#"[{"name":"Alice"},{"name":"Bob"}]"#.as_slice(),
194 &profile,
195 |quin| quins.push(quin),
196 )
197 .expect("top-level arrays containing objects are valid JSON mappings");
198
199 assert_eq!(quins.len(), 2);
200 assert!(quins.iter().all(|quin| quin.predicate == 42));
201 }
202}