nodedb_types/value/
json.rs1use std::str::FromStr;
6
7use super::core::Value;
8
9impl From<Value> for serde_json::Value {
10 fn from(v: Value) -> Self {
11 match v {
12 Value::Null => serde_json::Value::Null,
13 Value::Bool(b) => serde_json::Value::Bool(b),
14 Value::Integer(i) => serde_json::json!(i),
15 Value::Float(f) => serde_json::json!(f),
16 Value::String(s) | Value::Uuid(s) | Value::Ulid(s) | Value::Regex(s) => {
17 serde_json::Value::String(s)
18 }
19 Value::Bytes(b) => {
20 let hex: String = b.iter().map(|byte| format!("{byte:02x}")).collect();
21 serde_json::Value::String(hex)
22 }
23 Value::Array(arr) | Value::Set(arr) => {
24 serde_json::Value::Array(arr.into_iter().map(serde_json::Value::from).collect())
25 }
26 Value::Object(map) => serde_json::Value::Object(
27 map.into_iter()
28 .map(|(k, v)| (k, serde_json::Value::from(v)))
29 .collect(),
30 ),
31 Value::DateTime(dt) | Value::NaiveDateTime(dt) => {
32 serde_json::Value::String(dt.to_string())
33 }
34 Value::Duration(d) => serde_json::Value::String(d.to_string()),
35 Value::Decimal(d) => {
36 let s = d.to_string();
40 serde_json::Number::from_str(&s)
41 .map(serde_json::Value::Number)
42 .unwrap_or_else(|_| serde_json::Value::String(s))
43 }
44 Value::Geometry(g) => serde_json::to_value(g).unwrap_or(serde_json::Value::Null),
45 Value::Range { .. } | Value::Record { .. } => serde_json::Value::Null,
46 Value::Vector(v) => {
47 serde_json::Value::Array(v.iter().map(|f| serde_json::json!(*f)).collect())
48 }
49 Value::ArrayCell(cell) => {
50 let mut obj = serde_json::json!({
51 "coords": cell.coords.into_iter().map(serde_json::Value::from).collect::<Vec<_>>(),
52 "attrs": cell.attrs.into_iter().map(serde_json::Value::from).collect::<Vec<_>>(),
53 });
54 if let Some(ts) = cell.system_time {
55 obj["_ts_system"] = serde_json::json!(ts);
56 }
57 obj
58 }
59 }
60 }
61}
62
63impl From<serde_json::Value> for Value {
64 fn from(v: serde_json::Value) -> Self {
65 match v {
66 serde_json::Value::Null => Value::Null,
67 serde_json::Value::Bool(b) => Value::Bool(b),
68 serde_json::Value::Number(n) => {
69 if let Some(i) = n.as_i64() {
70 Value::Integer(i)
71 } else if let Some(u) = n.as_u64() {
72 Value::Integer(u as i64)
73 } else if let Some(f) = n.as_f64() {
74 Value::Float(f)
75 } else {
76 Value::Null
77 }
78 }
79 serde_json::Value::String(s) => Value::String(s),
80 serde_json::Value::Array(arr) => {
81 Value::Array(arr.into_iter().map(Value::from).collect())
82 }
83 serde_json::Value::Object(map) => {
84 Value::Object(map.into_iter().map(|(k, v)| (k, Value::from(v))).collect())
85 }
86 }
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93 use crate::array_cell::ArrayCell;
94
95 #[test]
96 fn decimal_to_json_is_number_not_string() {
97 let d = rust_decimal::Decimal::new(12345, 2); let json = serde_json::Value::from(Value::Decimal(d));
99 assert!(json.is_number(), "expected JSON Number, got {json:?}");
100 assert_eq!(json.to_string(), "123.45");
101 }
102
103 #[test]
109 fn json_lossy_uuid_becomes_string() {
110 let v = Value::Uuid("550e8400-e29b-41d4-a716-446655440000".into());
111 let json = serde_json::Value::from(v);
112 assert!(json.is_string(), "Uuid must serialize as JSON string");
113 let rt = Value::from(json);
115 assert!(
116 matches!(rt, Value::String(_)),
117 "Uuid round-trips through JSON as String, got {rt:?}"
118 );
119 }
120
121 #[test]
122 fn json_lossy_ulid_becomes_string() {
123 let v = Value::Ulid("01ARZ3NDEKTSV4RRFFQ69G5FAV".into());
124 let json = serde_json::Value::from(v);
125 assert!(json.is_string(), "Ulid must serialize as JSON string");
126 let rt = Value::from(json);
127 assert!(
128 matches!(rt, Value::String(_)),
129 "Ulid round-trips through JSON as String, got {rt:?}"
130 );
131 }
132
133 #[test]
134 fn json_lossy_regex_becomes_string() {
135 let v = Value::Regex(r"^\d+$".into());
136 let json = serde_json::Value::from(v);
137 assert!(json.is_string(), "Regex must serialize as JSON string");
138 let rt = Value::from(json);
139 assert!(
140 matches!(rt, Value::String(_)),
141 "Regex round-trips through JSON as String, got {rt:?}"
142 );
143 }
144
145 #[test]
146 fn json_lossy_range_becomes_null() {
147 let v = Value::Range {
148 start: Some(Box::new(Value::Integer(1))),
149 end: Some(Box::new(Value::Integer(10))),
150 inclusive: false,
151 };
152 let json = serde_json::Value::from(v);
153 assert!(
154 json.is_null(),
155 "Range must serialize as JSON null, got {json:?}"
156 );
157 let rt = Value::from(json);
158 assert!(
159 matches!(rt, Value::Null),
160 "Range round-trips through JSON as Null, got {rt:?}"
161 );
162 }
163
164 #[test]
165 fn json_lossy_record_becomes_null() {
166 let v = Value::Record {
167 table: "users".into(),
168 id: "abc123".into(),
169 };
170 let json = serde_json::Value::from(v);
171 assert!(
172 json.is_null(),
173 "Record must serialize as JSON null, got {json:?}"
174 );
175 let rt = Value::from(json);
176 assert!(
177 matches!(rt, Value::Null),
178 "Record round-trips through JSON as Null, got {rt:?}"
179 );
180 }
181
182 #[test]
183 fn json_lossy_array_cell_becomes_object_without_discriminator() {
184 let v = Value::ArrayCell(ArrayCell {
185 coords: vec![Value::Integer(1), Value::Integer(2)],
186 attrs: vec![Value::Float(3.5)],
187 system_time: None,
188 });
189 let json = serde_json::Value::from(v);
190 assert!(
191 json.is_object(),
192 "ArrayCell must serialize as JSON object, got {json:?}"
193 );
194 let obj = json.as_object().unwrap();
196 assert!(
197 obj.contains_key("coords"),
198 "ArrayCell JSON must have 'coords' key"
199 );
200 assert!(
201 obj.contains_key("attrs"),
202 "ArrayCell JSON must have 'attrs' key"
203 );
204 let rt = Value::from(json);
206 assert!(
207 matches!(rt, Value::Object(_)),
208 "ArrayCell round-trips through JSON as Object, got {rt:?}"
209 );
210 }
211}