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) => super::float_text::float_to_json(f),
18 Value::String(s) | Value::Uuid(s) | Value::Ulid(s) | Value::Regex(s) => {
19 serde_json::Value::String(s)
20 }
21 Value::Bytes(b) => serde_json::Value::String(hex::encode(b)),
22 Value::Array(arr) | Value::Set(arr) => {
23 serde_json::Value::Array(arr.into_iter().map(serde_json::Value::from).collect())
24 }
25 Value::Object(map) => serde_json::Value::Object(
26 map.into_iter()
27 .map(|(k, v)| (k, serde_json::Value::from(v)))
28 .collect(),
29 ),
30 Value::DateTime(dt) | Value::NaiveDateTime(dt) => {
31 serde_json::Value::String(dt.to_string())
32 }
33 Value::Duration(d) => serde_json::Value::String(d.to_string()),
34 Value::Decimal(d) => {
35 let s = d.to_string();
41 match serde_json::Number::from_str(&s) {
42 Ok(n) if !(d.scale() == 0 && n.is_f64()) => serde_json::Value::Number(n),
43 _ => serde_json::Value::String(s),
44 }
45 }
46 Value::Geometry(g) => serde_json::to_value(g).unwrap_or(serde_json::Value::Null),
47 Value::Range { .. } | Value::Record { .. } => serde_json::Value::Null,
48 Value::Vector(v) => {
49 serde_json::Value::Array(
52 v.iter()
53 .map(|f| {
54 if f.is_finite() {
55 serde_json::json!(*f)
56 } else {
57 super::float_text::float_to_json(f64::from(*f))
58 }
59 })
60 .collect(),
61 )
62 }
63 Value::ArrayCell(cell) => {
64 let mut obj = serde_json::json!({
65 "coords": cell.coords.into_iter().map(serde_json::Value::from).collect::<Vec<_>>(),
66 "attrs": cell.attrs.into_iter().map(serde_json::Value::from).collect::<Vec<_>>(),
67 });
68 if let Some(ts) = cell.system_time {
69 obj["_ts_system"] = serde_json::json!(ts);
70 }
71 obj
72 }
73 }
74 }
75}
76
77impl From<serde_json::Value> for Value {
78 fn from(v: serde_json::Value) -> Self {
79 match v {
80 serde_json::Value::Null => Value::Null,
81 serde_json::Value::Bool(b) => Value::Bool(b),
82 serde_json::Value::Number(n) => {
83 if let Some(i) = n.as_i64() {
84 Value::Integer(i)
85 } else if let Some(u) = n.as_u64() {
86 Value::from_u64(u)
87 } else if let Some(f) = n.as_f64() {
88 Value::Float(f)
89 } else {
90 Value::Null
91 }
92 }
93 serde_json::Value::String(s) => Value::String(s),
94 serde_json::Value::Array(arr) => {
95 Value::Array(arr.into_iter().map(Value::from).collect())
96 }
97 serde_json::Value::Object(map) => {
98 Value::Object(map.into_iter().map(|(k, v)| (k, Value::from(v))).collect())
99 }
100 }
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107 use crate::array_cell::ArrayCell;
108
109 #[test]
110 fn json_u64_above_i64_max_keeps_its_number() {
111 let json: serde_json::Value =
112 sonic_rs::from_str("18446744073709551615").expect("parse u64::MAX");
113 assert_eq!(
114 Value::from(json),
115 Value::Decimal(rust_decimal::Decimal::from(u64::MAX))
116 );
117 let small: serde_json::Value = sonic_rs::from_str("42").expect("parse 42");
118 assert_eq!(Value::from(small), Value::Integer(42));
119 }
120
121 #[test]
122 fn integer_decimal_past_64_bits_keeps_exact_digits() {
123 let big = rust_decimal::Decimal::from_i128_with_scale(2 * i128::from(u64::MAX), 0);
124 let json = serde_json::Value::from(Value::Decimal(big));
125 assert_eq!(
126 json,
127 serde_json::Value::String("36893488147419103230".into())
128 );
129 let max = serde_json::Value::from(Value::Decimal(rust_decimal::Decimal::from(u64::MAX)));
130 assert_eq!(max, serde_json::json!(u64::MAX));
131 }
132
133 #[test]
134 fn decimal_to_json_is_number_not_string() {
135 let d = rust_decimal::Decimal::new(12345, 2); let json = serde_json::Value::from(Value::Decimal(d));
137 assert!(json.is_number(), "expected JSON Number, got {json:?}");
138 assert_eq!(json.to_string(), "123.45");
139 }
140
141 #[test]
147 fn json_lossy_uuid_becomes_string() {
148 let v = Value::Uuid("550e8400-e29b-41d4-a716-446655440000".into());
149 let json = serde_json::Value::from(v);
150 assert!(json.is_string(), "Uuid must serialize as JSON string");
151 let rt = Value::from(json);
153 assert!(
154 matches!(rt, Value::String(_)),
155 "Uuid round-trips through JSON as String, got {rt:?}"
156 );
157 }
158
159 #[test]
160 fn json_lossy_ulid_becomes_string() {
161 let v = Value::Ulid("01ARZ3NDEKTSV4RRFFQ69G5FAV".into());
162 let json = serde_json::Value::from(v);
163 assert!(json.is_string(), "Ulid must serialize as JSON string");
164 let rt = Value::from(json);
165 assert!(
166 matches!(rt, Value::String(_)),
167 "Ulid round-trips through JSON as String, got {rt:?}"
168 );
169 }
170
171 #[test]
172 fn json_lossy_regex_becomes_string() {
173 let v = Value::Regex(r"^\d+$".into());
174 let json = serde_json::Value::from(v);
175 assert!(json.is_string(), "Regex must serialize as JSON string");
176 let rt = Value::from(json);
177 assert!(
178 matches!(rt, Value::String(_)),
179 "Regex round-trips through JSON as String, got {rt:?}"
180 );
181 }
182
183 #[test]
184 fn json_lossy_range_becomes_null() {
185 let v = Value::Range {
186 start: Some(Box::new(Value::Integer(1))),
187 end: Some(Box::new(Value::Integer(10))),
188 inclusive: false,
189 };
190 let json = serde_json::Value::from(v);
191 assert!(
192 json.is_null(),
193 "Range must serialize as JSON null, got {json:?}"
194 );
195 let rt = Value::from(json);
196 assert!(
197 matches!(rt, Value::Null),
198 "Range round-trips through JSON as Null, got {rt:?}"
199 );
200 }
201
202 #[test]
203 fn json_lossy_record_becomes_null() {
204 let v = Value::Record {
205 table: "users".into(),
206 id: "abc123".into(),
207 };
208 let json = serde_json::Value::from(v);
209 assert!(
210 json.is_null(),
211 "Record must serialize as JSON null, got {json:?}"
212 );
213 let rt = Value::from(json);
214 assert!(
215 matches!(rt, Value::Null),
216 "Record round-trips through JSON as Null, got {rt:?}"
217 );
218 }
219
220 #[test]
221 fn json_lossy_array_cell_becomes_object_without_discriminator() {
222 let v = Value::ArrayCell(ArrayCell {
223 coords: vec![Value::Integer(1), Value::Integer(2)],
224 attrs: vec![Value::Float(3.5)],
225 system_time: None,
226 });
227 let json = serde_json::Value::from(v);
228 assert!(
229 json.is_object(),
230 "ArrayCell must serialize as JSON object, got {json:?}"
231 );
232 let obj = json.as_object().unwrap();
234 assert!(
235 obj.contains_key("coords"),
236 "ArrayCell JSON must have 'coords' key"
237 );
238 assert!(
239 obj.contains_key("attrs"),
240 "ArrayCell JSON must have 'attrs' key"
241 );
242 let rt = Value::from(json);
244 assert!(
245 matches!(rt, Value::Object(_)),
246 "ArrayCell round-trips through JSON as Object, got {rt:?}"
247 );
248 }
249
250 #[test]
253 fn non_finite_floats_render_postgres_text_not_null() {
254 assert_eq!(
255 serde_json::Value::from(Value::Float(f64::NAN)),
256 serde_json::json!("NaN")
257 );
258 assert_eq!(
259 serde_json::Value::from(Value::Array(vec![
260 Value::Float(f64::INFINITY),
261 Value::Float(f64::NEG_INFINITY),
262 Value::Float(1.5),
263 ])),
264 serde_json::json!(["Infinity", "-Infinity", 1.5])
265 );
266 assert_eq!(
267 serde_json::Value::from(Value::Vector(vec![0.5, f32::NAN].into())),
268 serde_json::json!([0.5, "NaN"])
269 );
270 }
271}