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) => serde_json::json!({
50 "coords": cell.coords.into_iter().map(serde_json::Value::from).collect::<Vec<_>>(),
51 "attrs": cell.attrs.into_iter().map(serde_json::Value::from).collect::<Vec<_>>(),
52 }),
53 }
54 }
55}
56
57impl From<serde_json::Value> for Value {
58 fn from(v: serde_json::Value) -> Self {
59 match v {
60 serde_json::Value::Null => Value::Null,
61 serde_json::Value::Bool(b) => Value::Bool(b),
62 serde_json::Value::Number(n) => {
63 if let Some(i) = n.as_i64() {
64 Value::Integer(i)
65 } else if let Some(u) = n.as_u64() {
66 Value::Integer(u as i64)
67 } else if let Some(f) = n.as_f64() {
68 Value::Float(f)
69 } else {
70 Value::Null
71 }
72 }
73 serde_json::Value::String(s) => Value::String(s),
74 serde_json::Value::Array(arr) => {
75 Value::Array(arr.into_iter().map(Value::from).collect())
76 }
77 serde_json::Value::Object(map) => {
78 Value::Object(map.into_iter().map(|(k, v)| (k, Value::from(v))).collect())
79 }
80 }
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87 use crate::array_cell::ArrayCell;
88
89 #[test]
90 fn decimal_to_json_is_number_not_string() {
91 let d = rust_decimal::Decimal::new(12345, 2); let json = serde_json::Value::from(Value::Decimal(d));
93 assert!(json.is_number(), "expected JSON Number, got {json:?}");
94 assert_eq!(json.to_string(), "123.45");
95 }
96
97 #[test]
103 fn json_lossy_uuid_becomes_string() {
104 let v = Value::Uuid("550e8400-e29b-41d4-a716-446655440000".into());
105 let json = serde_json::Value::from(v);
106 assert!(json.is_string(), "Uuid must serialize as JSON string");
107 let rt = Value::from(json);
109 assert!(
110 matches!(rt, Value::String(_)),
111 "Uuid round-trips through JSON as String, got {rt:?}"
112 );
113 }
114
115 #[test]
116 fn json_lossy_ulid_becomes_string() {
117 let v = Value::Ulid("01ARZ3NDEKTSV4RRFFQ69G5FAV".into());
118 let json = serde_json::Value::from(v);
119 assert!(json.is_string(), "Ulid must serialize as JSON string");
120 let rt = Value::from(json);
121 assert!(
122 matches!(rt, Value::String(_)),
123 "Ulid round-trips through JSON as String, got {rt:?}"
124 );
125 }
126
127 #[test]
128 fn json_lossy_regex_becomes_string() {
129 let v = Value::Regex(r"^\d+$".into());
130 let json = serde_json::Value::from(v);
131 assert!(json.is_string(), "Regex must serialize as JSON string");
132 let rt = Value::from(json);
133 assert!(
134 matches!(rt, Value::String(_)),
135 "Regex round-trips through JSON as String, got {rt:?}"
136 );
137 }
138
139 #[test]
140 fn json_lossy_range_becomes_null() {
141 let v = Value::Range {
142 start: Some(Box::new(Value::Integer(1))),
143 end: Some(Box::new(Value::Integer(10))),
144 inclusive: false,
145 };
146 let json = serde_json::Value::from(v);
147 assert!(
148 json.is_null(),
149 "Range must serialize as JSON null, got {json:?}"
150 );
151 let rt = Value::from(json);
152 assert!(
153 matches!(rt, Value::Null),
154 "Range round-trips through JSON as Null, got {rt:?}"
155 );
156 }
157
158 #[test]
159 fn json_lossy_record_becomes_null() {
160 let v = Value::Record {
161 table: "users".into(),
162 id: "abc123".into(),
163 };
164 let json = serde_json::Value::from(v);
165 assert!(
166 json.is_null(),
167 "Record must serialize as JSON null, got {json:?}"
168 );
169 let rt = Value::from(json);
170 assert!(
171 matches!(rt, Value::Null),
172 "Record round-trips through JSON as Null, got {rt:?}"
173 );
174 }
175
176 #[test]
177 fn json_lossy_array_cell_becomes_object_without_discriminator() {
178 let v = Value::ArrayCell(ArrayCell {
179 coords: vec![Value::Integer(1), Value::Integer(2)],
180 attrs: vec![Value::Float(3.5)],
181 });
182 let json = serde_json::Value::from(v);
183 assert!(
184 json.is_object(),
185 "ArrayCell must serialize as JSON object, got {json:?}"
186 );
187 let obj = json.as_object().unwrap();
189 assert!(
190 obj.contains_key("coords"),
191 "ArrayCell JSON must have 'coords' key"
192 );
193 assert!(
194 obj.contains_key("attrs"),
195 "ArrayCell JSON must have 'attrs' key"
196 );
197 let rt = Value::from(json);
199 assert!(
200 matches!(rt, Value::Object(_)),
201 "ArrayCell round-trips through JSON as Object, got {rt:?}"
202 );
203 }
204}