1use crate::codecs::shared;
4use crate::codecs::{Codec, CodecError};
5use crate::data::{HCol, HDict, HGrid};
6use crate::kinds::*;
7use chrono::{NaiveDate, NaiveTime, Timelike};
8use serde_json::{Map, Value};
9
10pub struct Json4Codec;
14
15impl Codec for Json4Codec {
16 fn mime_type(&self) -> &str {
17 "application/json"
18 }
19
20 fn encode_grid(&self, grid: &HGrid) -> Result<String, CodecError> {
21 let val = encode_grid_value(grid)?;
22 serde_json::to_string(&val).map_err(|e| CodecError::Encode(e.to_string()))
23 }
24
25 fn decode_grid(&self, input: &str) -> Result<HGrid, CodecError> {
26 let val: Value = serde_json::from_str(input).map_err(|e| CodecError::Parse {
27 pos: 0,
28 message: e.to_string(),
29 })?;
30 decode_grid_value(&val)
31 }
32
33 fn encode_scalar(&self, val: &Kind) -> Result<String, CodecError> {
34 let json = encode_kind(val)?;
35 serde_json::to_string(&json).map_err(|e| CodecError::Encode(e.to_string()))
36 }
37
38 fn decode_scalar(&self, input: &str) -> Result<Kind, CodecError> {
39 let val: Value = serde_json::from_str(input).map_err(|e| CodecError::Parse {
40 pos: 0,
41 message: e.to_string(),
42 })?;
43 decode_kind(&val)
44 }
45}
46
47pub fn encode_kind(val: &Kind) -> Result<Value, CodecError> {
51 match val {
52 Kind::Null => Ok(Value::Null),
53 Kind::Bool(b) => Ok(Value::Bool(*b)),
54 Kind::Marker => Ok(kind_obj("marker", |_| {})),
55 Kind::NA => Ok(kind_obj("na", |_| {})),
56 Kind::Remove => Ok(kind_obj("remove", |_| {})),
57 Kind::Number(n) => Ok(encode_number(n)),
58 Kind::Str(s) => Ok(Value::String(s.clone())),
59 Kind::Ref(r) => Ok(encode_ref(r)),
60 Kind::Uri(u) => Ok(kind_obj("uri", |m| {
61 m.insert("val".into(), Value::String(u.val().to_string()));
62 })),
63 Kind::Symbol(s) => Ok(kind_obj("symbol", |m| {
64 m.insert("val".into(), Value::String(s.val().to_string()));
65 })),
66 Kind::Date(d) => Ok(kind_obj("date", |m| {
67 m.insert(
68 "val".into(),
69 Value::String(d.format("%Y-%m-%d").to_string()),
70 );
71 })),
72 Kind::Time(t) => Ok(kind_obj("time", |m| {
73 m.insert("val".into(), Value::String(encode_time_str(t)));
74 })),
75 Kind::DateTime(hdt) => Ok(encode_datetime(hdt)),
76 Kind::Coord(c) => Ok(kind_obj("coord", |m| {
77 m.insert("lat".into(), json_number(c.lat));
78 m.insert("lng".into(), json_number(c.lng));
79 })),
80 Kind::XStr(x) => Ok(kind_obj("xstr", |m| {
81 m.insert("type".into(), Value::String(x.type_name.clone()));
82 m.insert("val".into(), Value::String(x.val.clone()));
83 })),
84 Kind::List(items) => {
85 let arr: Result<Vec<Value>, CodecError> = items.iter().map(encode_kind).collect();
86 Ok(Value::Array(arr?))
87 }
88 Kind::Dict(d) => encode_dict(d),
89 Kind::Grid(g) => encode_grid_value(g).map(Value::Object),
90 }
91}
92
93fn kind_obj(kind_name: &str, f: impl FnOnce(&mut Map<String, Value>)) -> Value {
95 let mut m = Map::new();
96 m.insert("_kind".into(), Value::String(kind_name.into()));
97 f(&mut m);
98 Value::Object(m)
99}
100
101fn encode_number(n: &Number) -> Value {
103 let mut m = Map::new();
104 m.insert("_kind".into(), Value::String("number".into()));
105 if n.val.is_infinite() {
106 if n.val > 0.0 {
107 m.insert("val".into(), Value::String("INF".into()));
108 } else {
109 m.insert("val".into(), Value::String("-INF".into()));
110 }
111 } else if n.val.is_nan() {
112 m.insert("val".into(), Value::String("NaN".into()));
113 } else {
114 m.insert("val".into(), json_number(n.val));
115 }
116 if let Some(ref u) = n.unit {
117 m.insert("unit".into(), Value::String(u.clone()));
118 }
119 Value::Object(m)
120}
121
122fn encode_ref(r: &HRef) -> Value {
124 kind_obj("ref", |m| {
125 m.insert("val".into(), Value::String(r.val.clone()));
126 if let Some(ref dis) = r.dis {
127 m.insert("dis".into(), Value::String(dis.clone()));
128 }
129 })
130}
131
132fn encode_datetime(hdt: &HDateTime) -> Value {
134 let dt_str = hdt.dt.format("%Y-%m-%dT%H:%M:%S").to_string();
135 let frac = shared::format_frac_seconds(hdt.dt.nanosecond());
136 let offset_str = hdt.dt.format("%:z").to_string();
137 let val_str = format!("{dt_str}{frac}{offset_str}");
138
139 kind_obj("dateTime", |m| {
140 m.insert("val".into(), Value::String(val_str));
141 if !hdt.tz_name.is_empty() {
142 m.insert("tz".into(), Value::String(hdt.tz_name.clone()));
143 }
144 })
145}
146
147fn encode_time_str(t: &NaiveTime) -> String {
149 shared::format_time(t)
150}
151
152fn encode_dict(d: &HDict) -> Result<Value, CodecError> {
154 let mut m = Map::new();
155 for (k, v) in d.sorted_tags() {
156 m.insert(k.to_string(), encode_kind(v)?);
157 }
158 Ok(Value::Object(m))
159}
160
161fn encode_grid_value(grid: &HGrid) -> Result<Map<String, Value>, CodecError> {
163 let mut m = Map::new();
164 m.insert("_kind".into(), Value::String("grid".into()));
165
166 if !grid.meta.is_empty() {
168 let meta_val = encode_dict(&grid.meta)?;
169 m.insert("meta".into(), meta_val);
170 }
171
172 let cols: Result<Vec<Value>, CodecError> = grid
174 .cols
175 .iter()
176 .map(|col| {
177 let mut cm = Map::new();
178 cm.insert("name".into(), Value::String(col.name.clone()));
179 if !col.meta.is_empty() {
180 let meta_val = encode_dict(&col.meta)?;
181 cm.insert("meta".into(), meta_val);
182 }
183 Ok(Value::Object(cm))
184 })
185 .collect();
186 m.insert("cols".into(), Value::Array(cols?));
187
188 let rows: Result<Vec<Value>, CodecError> = grid.rows.iter().map(encode_dict).collect();
190 m.insert("rows".into(), Value::Array(rows?));
191
192 Ok(m)
193}
194
195fn json_number(v: f64) -> Value {
197 match serde_json::Number::from_f64(v) {
199 Some(n) => Value::Number(n),
200 None => Value::String(format!("{v}")),
201 }
202}
203
204const MAX_NESTING_DEPTH: usize = 64;
208
209pub fn decode_kind(val: &Value) -> Result<Kind, CodecError> {
211 decode_kind_depth(val, 0)
212}
213
214fn decode_kind_depth(val: &Value, depth: usize) -> Result<Kind, CodecError> {
216 if depth > MAX_NESTING_DEPTH {
217 return Err(CodecError::Parse {
218 pos: 0,
219 message: "maximum nesting depth exceeded".into(),
220 });
221 }
222 match val {
223 Value::Null => Ok(Kind::Null),
224 Value::Bool(b) => Ok(Kind::Bool(*b)),
225 Value::Number(n) => {
226 let v = n.as_f64().ok_or_else(|| CodecError::Parse {
228 pos: 0,
229 message: format!("cannot convert JSON number to f64: {n}"),
230 })?;
231 Ok(Kind::Number(Number::unitless(v)))
232 }
233 Value::String(s) => {
234 Ok(Kind::Str(s.clone()))
236 }
237 Value::Array(arr) => {
238 let items: Result<Vec<Kind>, CodecError> = arr
239 .iter()
240 .map(|v| decode_kind_depth(v, depth + 1))
241 .collect();
242 Ok(Kind::List(items?))
243 }
244 Value::Object(m) => decode_object_depth(m, depth + 1),
245 }
246}
247
248fn decode_object_depth(m: &Map<String, Value>, depth: usize) -> Result<Kind, CodecError> {
250 if depth > MAX_NESTING_DEPTH {
251 return Err(CodecError::Parse {
252 pos: 0,
253 message: "maximum nesting depth exceeded".into(),
254 });
255 }
256 match m.get("_kind") {
257 Some(Value::String(k)) => decode_typed_depth(k, m, depth),
258 _ => {
259 let mut dict = HDict::new();
261 for (key, val) in m {
262 dict.set(key.clone(), decode_kind_depth(val, depth + 1)?);
263 }
264 Ok(Kind::Dict(Box::new(dict)))
265 }
266 }
267}
268
269fn decode_typed_depth(
271 kind: &str,
272 m: &Map<String, Value>,
273 depth: usize,
274) -> Result<Kind, CodecError> {
275 match kind {
276 "marker" => Ok(Kind::Marker),
277 "na" => Ok(Kind::NA),
278 "remove" => Ok(Kind::Remove),
279 "number" => decode_number(m),
280 "ref" => decode_ref(m),
281 "uri" => {
282 let val = get_str(m, "val")?;
283 Ok(Kind::Uri(Uri::new(val)))
284 }
285 "symbol" => {
286 let val = get_str(m, "val")?;
287 Ok(Kind::Symbol(Symbol::new(val)))
288 }
289 "date" => {
290 let val = get_str(m, "val")?;
291 let d = NaiveDate::parse_from_str(&val, "%Y-%m-%d").map_err(|e| CodecError::Parse {
292 pos: 0,
293 message: format!("invalid date: {e}"),
294 })?;
295 Ok(Kind::Date(d))
296 }
297 "time" => {
298 let val = get_str(m, "val")?;
299 let t = parse_time(&val)?;
300 Ok(Kind::Time(t))
301 }
302 "dateTime" => decode_datetime(m),
303 "coord" => decode_coord(m),
304 "xstr" => {
305 let type_name = get_str(m, "type")?;
306 let val = get_str(m, "val")?;
307 Ok(Kind::XStr(XStr::new(type_name, val)))
308 }
309 "grid" => {
310 let grid = decode_grid_value_depth(&Value::Object(m.clone()), depth)?;
311 Ok(Kind::Grid(Box::new(grid)))
312 }
313 other => Err(CodecError::Parse {
314 pos: 0,
315 message: format!("unknown _kind: {other}"),
316 }),
317 }
318}
319
320fn decode_number(m: &Map<String, Value>) -> Result<Kind, CodecError> {
322 let val = m.get("val").ok_or_else(|| CodecError::Parse {
323 pos: 0,
324 message: "number missing 'val' field".into(),
325 })?;
326 let v = match val {
327 Value::Number(n) => n.as_f64().ok_or_else(|| CodecError::Parse {
328 pos: 0,
329 message: "cannot convert number val to f64".into(),
330 })?,
331 Value::String(s) => match s.as_str() {
332 "INF" => f64::INFINITY,
333 "-INF" => f64::NEG_INFINITY,
334 "NaN" => f64::NAN,
335 _ => s.parse::<f64>().map_err(|e| CodecError::Parse {
336 pos: 0,
337 message: format!("invalid number string: {e}"),
338 })?,
339 },
340 _ => {
341 return Err(CodecError::Parse {
342 pos: 0,
343 message: "number 'val' must be a number or string".into(),
344 });
345 }
346 };
347 let unit = match m.get("unit") {
348 Some(Value::String(u)) => Some(u.clone()),
349 _ => None,
350 };
351 Ok(Kind::Number(Number::new(v, unit)))
352}
353
354fn decode_ref(m: &Map<String, Value>) -> Result<Kind, CodecError> {
356 let val = get_str(m, "val")?;
357 let dis = match m.get("dis") {
358 Some(Value::String(d)) => Some(d.clone()),
359 _ => None,
360 };
361 Ok(Kind::Ref(HRef::new(val, dis)))
362}
363
364fn decode_datetime(m: &Map<String, Value>) -> Result<Kind, CodecError> {
366 let val = get_str(m, "val")?;
367 let dt = chrono::DateTime::parse_from_rfc3339(&val)
368 .or_else(|_| {
369 chrono::DateTime::parse_from_str(&val, "%Y-%m-%dT%H:%M:%S%:z")
371 })
372 .map_err(|e| CodecError::Parse {
373 pos: 0,
374 message: format!("invalid datetime: {e}"),
375 })?;
376 let tz = match m.get("tz") {
377 Some(Value::String(t)) => t.clone(),
378 _ => String::new(),
379 };
380 Ok(Kind::DateTime(HDateTime::new(dt, tz)))
381}
382
383fn decode_coord(m: &Map<String, Value>) -> Result<Kind, CodecError> {
385 let lat = get_f64(m, "lat")?;
386 let lng = get_f64(m, "lng")?;
387 Ok(Kind::Coord(Coord::new(lat, lng)))
388}
389
390pub fn decode_grid_value(val: &Value) -> Result<HGrid, CodecError> {
392 decode_grid_value_depth(val, 0)
393}
394
395fn decode_grid_value_depth(val: &Value, depth: usize) -> Result<HGrid, CodecError> {
397 if depth > MAX_NESTING_DEPTH {
398 return Err(CodecError::Parse {
399 pos: 0,
400 message: "maximum nesting depth exceeded".into(),
401 });
402 }
403
404 let m = match val {
405 Value::Object(m) => m,
406 _ => {
407 return Err(CodecError::Parse {
408 pos: 0,
409 message: "grid must be a JSON object".into(),
410 });
411 }
412 };
413
414 let meta = match m.get("meta") {
416 Some(Value::Object(meta_map)) => {
417 let mut dict = HDict::new();
418 for (key, val) in meta_map {
419 if key == "_kind" {
420 continue; }
422 dict.set(key.clone(), decode_kind_depth(val, depth + 1)?);
423 }
424 dict
425 }
426 _ => HDict::new(),
427 };
428
429 let cols = match m.get("cols") {
431 Some(Value::Array(arr)) => {
432 let mut cols = Vec::with_capacity(arr.len());
433 for col_val in arr {
434 let col_obj = col_val.as_object().ok_or_else(|| CodecError::Parse {
435 pos: 0,
436 message: "col must be a JSON object".into(),
437 })?;
438 let name = get_str(col_obj, "name")?;
439 let col_meta = match col_obj.get("meta") {
440 Some(Value::Object(meta_map)) => {
441 let mut dict = HDict::new();
442 for (key, val) in meta_map {
443 dict.set(key.clone(), decode_kind_depth(val, depth + 1)?);
444 }
445 dict
446 }
447 _ => HDict::new(),
448 };
449 cols.push(HCol::with_meta(name, col_meta));
450 }
451 cols
452 }
453 _ => Vec::new(),
454 };
455
456 let rows = match m.get("rows") {
458 Some(Value::Array(arr)) => {
459 let mut rows = Vec::with_capacity(arr.len());
460 for row_val in arr {
461 let row_obj = row_val.as_object().ok_or_else(|| CodecError::Parse {
462 pos: 0,
463 message: "row must be a JSON object".into(),
464 })?;
465 let mut dict = HDict::new();
466 for (key, val) in row_obj {
467 dict.set(key.clone(), decode_kind_depth(val, depth + 1)?);
468 }
469 rows.push(dict);
470 }
471 rows
472 }
473 _ => Vec::new(),
474 };
475
476 Ok(HGrid::from_parts(meta, cols, rows))
477}
478
479fn parse_time(s: &str) -> Result<NaiveTime, CodecError> {
481 NaiveTime::parse_from_str(s, "%H:%M:%S%.f")
483 .or_else(|_| NaiveTime::parse_from_str(s, "%H:%M:%S"))
484 .map_err(|e| CodecError::Parse {
485 pos: 0,
486 message: format!("invalid time: {e}"),
487 })
488}
489
490fn get_str(m: &Map<String, Value>, key: &str) -> Result<String, CodecError> {
492 match m.get(key) {
493 Some(Value::String(s)) => Ok(s.clone()),
494 _ => Err(CodecError::Parse {
495 pos: 0,
496 message: format!("missing or invalid string field '{key}'"),
497 }),
498 }
499}
500
501fn get_f64(m: &Map<String, Value>, key: &str) -> Result<f64, CodecError> {
503 match m.get(key) {
504 Some(Value::Number(n)) => n.as_f64().ok_or_else(|| CodecError::Parse {
505 pos: 0,
506 message: format!("cannot convert '{key}' to f64"),
507 }),
508 _ => Err(CodecError::Parse {
509 pos: 0,
510 message: format!("missing or invalid number field '{key}'"),
511 }),
512 }
513}
514
515#[cfg(test)]
516mod tests {
517 use super::*;
518 use crate::data::{HCol, HDict, HGrid};
519 use chrono::{FixedOffset, NaiveDate, NaiveTime, TimeZone};
520
521 fn roundtrip_scalar(kind: Kind) -> Kind {
522 let codec = Json4Codec;
523 let encoded = codec.encode_scalar(&kind).unwrap();
524 codec.decode_scalar(&encoded).unwrap()
525 }
526
527 #[test]
530 fn null_roundtrip() {
531 assert_eq!(roundtrip_scalar(Kind::Null), Kind::Null);
532 }
533
534 #[test]
535 fn null_encodes_to_json_null() {
536 let codec = Json4Codec;
537 assert_eq!(codec.encode_scalar(&Kind::Null).unwrap(), "null");
538 }
539
540 #[test]
543 fn bool_true_roundtrip() {
544 assert_eq!(roundtrip_scalar(Kind::Bool(true)), Kind::Bool(true));
545 }
546
547 #[test]
548 fn bool_false_roundtrip() {
549 assert_eq!(roundtrip_scalar(Kind::Bool(false)), Kind::Bool(false));
550 }
551
552 #[test]
553 fn bool_encodes_to_json_bool() {
554 let codec = Json4Codec;
555 assert_eq!(codec.encode_scalar(&Kind::Bool(true)).unwrap(), "true");
556 assert_eq!(codec.encode_scalar(&Kind::Bool(false)).unwrap(), "false");
557 }
558
559 #[test]
562 fn marker_roundtrip() {
563 assert_eq!(roundtrip_scalar(Kind::Marker), Kind::Marker);
564 }
565
566 #[test]
567 fn marker_encodes_with_kind() {
568 let codec = Json4Codec;
569 let encoded = codec.encode_scalar(&Kind::Marker).unwrap();
570 assert!(encoded.contains("\"_kind\""));
571 assert!(encoded.contains("\"marker\""));
572 }
573
574 #[test]
577 fn na_roundtrip() {
578 assert_eq!(roundtrip_scalar(Kind::NA), Kind::NA);
579 }
580
581 #[test]
584 fn remove_roundtrip() {
585 assert_eq!(roundtrip_scalar(Kind::Remove), Kind::Remove);
586 }
587
588 #[test]
591 fn number_unitless_roundtrip() {
592 let k = Kind::Number(Number::unitless(72.5));
593 assert_eq!(roundtrip_scalar(k.clone()), k);
594 }
595
596 #[test]
597 fn number_with_unit_roundtrip() {
598 let k = Kind::Number(Number::new(72.5, Some("\u{00B0}F".into())));
599 assert_eq!(roundtrip_scalar(k.clone()), k);
600 }
601
602 #[test]
603 fn number_zero_roundtrip() {
604 let k = Kind::Number(Number::unitless(0.0));
605 assert_eq!(roundtrip_scalar(k.clone()), k);
606 }
607
608 #[test]
609 fn number_negative_roundtrip() {
610 let k = Kind::Number(Number::new(-23.45, Some("m\u{00B2}".into())));
611 assert_eq!(roundtrip_scalar(k.clone()), k);
612 }
613
614 #[test]
615 fn number_inf_roundtrip() {
616 let k = Kind::Number(Number::unitless(f64::INFINITY));
617 assert_eq!(roundtrip_scalar(k.clone()), k);
618 }
619
620 #[test]
621 fn number_neg_inf_roundtrip() {
622 let k = Kind::Number(Number::unitless(f64::NEG_INFINITY));
623 assert_eq!(roundtrip_scalar(k.clone()), k);
624 }
625
626 #[test]
627 fn number_nan_roundtrip() {
628 let codec = Json4Codec;
629 let k = Kind::Number(Number::unitless(f64::NAN));
630 let encoded = codec.encode_scalar(&k).unwrap();
631 let decoded = codec.decode_scalar(&encoded).unwrap();
632 match decoded {
633 Kind::Number(n) => {
634 assert!(n.val.is_nan());
635 assert_eq!(n.unit, None);
636 }
637 other => panic!("expected Number, got {other:?}"),
638 }
639 }
640
641 #[test]
642 fn number_integer_roundtrip() {
643 let k = Kind::Number(Number::unitless(42.0));
644 assert_eq!(roundtrip_scalar(k.clone()), k);
645 }
646
647 #[test]
648 fn plain_json_number_decodes_as_number() {
649 let codec = Json4Codec;
650 let decoded = codec.decode_scalar("42.5").unwrap();
651 assert_eq!(decoded, Kind::Number(Number::unitless(42.5)));
652 }
653
654 #[test]
655 fn plain_json_integer_decodes_as_number() {
656 let codec = Json4Codec;
657 let decoded = codec.decode_scalar("100").unwrap();
658 assert_eq!(decoded, Kind::Number(Number::unitless(100.0)));
659 }
660
661 #[test]
664 fn string_simple_roundtrip() {
665 let k = Kind::Str("hello".into());
666 assert_eq!(roundtrip_scalar(k.clone()), k);
667 }
668
669 #[test]
670 fn string_empty_roundtrip() {
671 let k = Kind::Str(String::new());
672 assert_eq!(roundtrip_scalar(k.clone()), k);
673 }
674
675 #[test]
676 fn string_with_special_chars_roundtrip() {
677 let k = Kind::Str("line1\nline2\ttab".into());
678 assert_eq!(roundtrip_scalar(k.clone()), k);
679 }
680
681 #[test]
682 fn string_encodes_as_plain_json_string() {
683 let codec = Json4Codec;
684 let encoded = codec.encode_scalar(&Kind::Str("hello".into())).unwrap();
685 assert_eq!(encoded, "\"hello\"");
686 }
687
688 #[test]
689 fn plain_json_string_decodes_as_str() {
690 let codec = Json4Codec;
691 let decoded = codec.decode_scalar("\"world\"").unwrap();
692 assert_eq!(decoded, Kind::Str("world".into()));
693 }
694
695 #[test]
698 fn ref_simple_roundtrip() {
699 let k = Kind::Ref(HRef::from_val("site-1"));
700 assert_eq!(roundtrip_scalar(k.clone()), k);
701 }
702
703 #[test]
704 fn ref_with_dis_roundtrip() {
705 let k = Kind::Ref(HRef::new("site-1", Some("Main Site".into())));
706 let rt = roundtrip_scalar(k.clone());
707 match rt {
708 Kind::Ref(r) => {
709 assert_eq!(r.val, "site-1");
710 assert_eq!(r.dis, Some("Main Site".into()));
711 }
712 other => panic!("expected Ref, got {other:?}"),
713 }
714 }
715
716 #[test]
719 fn uri_roundtrip() {
720 let k = Kind::Uri(Uri::new("http://example.com/api"));
721 assert_eq!(roundtrip_scalar(k.clone()), k);
722 }
723
724 #[test]
727 fn symbol_roundtrip() {
728 let k = Kind::Symbol(Symbol::new("hot-water"));
729 assert_eq!(roundtrip_scalar(k.clone()), k);
730 }
731
732 #[test]
735 fn date_roundtrip() {
736 let k = Kind::Date(NaiveDate::from_ymd_opt(2024, 3, 13).unwrap());
737 assert_eq!(roundtrip_scalar(k.clone()), k);
738 }
739
740 #[test]
743 fn time_roundtrip() {
744 let k = Kind::Time(NaiveTime::from_hms_opt(8, 12, 5).unwrap());
745 assert_eq!(roundtrip_scalar(k.clone()), k);
746 }
747
748 #[test]
749 fn time_with_frac_roundtrip() {
750 let k = Kind::Time(NaiveTime::from_hms_milli_opt(14, 30, 0, 123).unwrap());
751 assert_eq!(roundtrip_scalar(k.clone()), k);
752 }
753
754 #[test]
757 fn datetime_roundtrip() {
758 let offset = FixedOffset::west_opt(5 * 3600).unwrap();
759 let dt = offset.with_ymd_and_hms(2024, 1, 1, 8, 12, 5).unwrap();
760 let k = Kind::DateTime(HDateTime::new(dt, "New_York"));
761 assert_eq!(roundtrip_scalar(k.clone()), k);
762 }
763
764 #[test]
765 fn datetime_utc_roundtrip() {
766 let offset = FixedOffset::east_opt(0).unwrap();
767 let dt = offset.with_ymd_and_hms(2024, 6, 15, 12, 0, 0).unwrap();
768 let k = Kind::DateTime(HDateTime::new(dt, "UTC"));
769 assert_eq!(roundtrip_scalar(k.clone()), k);
770 }
771
772 #[test]
775 fn coord_roundtrip() {
776 let k = Kind::Coord(Coord::new(37.5458266, -77.4491888));
777 assert_eq!(roundtrip_scalar(k.clone()), k);
778 }
779
780 #[test]
783 fn xstr_roundtrip() {
784 let k = Kind::XStr(XStr::new("Color", "red"));
785 assert_eq!(roundtrip_scalar(k.clone()), k);
786 }
787
788 #[test]
791 fn list_empty_roundtrip() {
792 let k = Kind::List(vec![]);
793 assert_eq!(roundtrip_scalar(k.clone()), k);
794 }
795
796 #[test]
797 fn list_mixed_roundtrip() {
798 let k = Kind::List(vec![
799 Kind::Number(Number::unitless(1.0)),
800 Kind::Str("two".into()),
801 Kind::Marker,
802 Kind::Bool(true),
803 Kind::Null,
804 ]);
805 assert_eq!(roundtrip_scalar(k.clone()), k);
806 }
807
808 #[test]
809 fn list_nested_roundtrip() {
810 let k = Kind::List(vec![
811 Kind::List(vec![Kind::Number(Number::unitless(1.0))]),
812 Kind::List(vec![Kind::Str("inner".into())]),
813 ]);
814 assert_eq!(roundtrip_scalar(k.clone()), k);
815 }
816
817 #[test]
820 fn dict_empty_roundtrip() {
821 let k = Kind::Dict(Box::new(HDict::new()));
822 assert_eq!(roundtrip_scalar(k.clone()), k);
823 }
824
825 #[test]
826 fn dict_with_values_roundtrip() {
827 let mut d = HDict::new();
828 d.set("site", Kind::Marker);
829 d.set("dis", Kind::Str("Main".into()));
830 d.set(
831 "area",
832 Kind::Number(Number::new(4500.0, Some("ft\u{00B2}".into()))),
833 );
834 let k = Kind::Dict(Box::new(d));
835 assert_eq!(roundtrip_scalar(k.clone()), k);
836 }
837
838 #[test]
839 fn dict_no_kind_key() {
840 let codec = Json4Codec;
842 let mut d = HDict::new();
843 d.set("site", Kind::Marker);
844 let k = Kind::Dict(Box::new(d));
845 let encoded = codec.encode_scalar(&k).unwrap();
846 let val: Value = serde_json::from_str(&encoded).unwrap();
848 let obj = val.as_object().unwrap();
849 assert!(obj.get("_kind").is_none());
850 }
851
852 #[test]
855 fn grid_empty_roundtrip() {
856 let codec = Json4Codec;
857 let g = HGrid::new();
858 let encoded = codec.encode_grid(&g).unwrap();
859 let decoded = codec.decode_grid(&encoded).unwrap();
860 assert!(decoded.is_empty());
861 assert_eq!(decoded.num_cols(), 0);
862 }
863
864 #[test]
865 fn grid_with_data_roundtrip() {
866 let codec = Json4Codec;
867
868 let cols = vec![HCol::new("dis"), HCol::new("area")];
869 let mut row1 = HDict::new();
870 row1.set("dis", Kind::Str("Site One".into()));
871 row1.set("area", Kind::Number(Number::unitless(4500.0)));
872 let mut row2 = HDict::new();
873 row2.set("dis", Kind::Str("Site Two".into()));
874 row2.set("area", Kind::Number(Number::unitless(3200.0)));
875
876 let g = HGrid::from_parts(HDict::new(), cols, vec![row1, row2]);
877 let encoded = codec.encode_grid(&g).unwrap();
878 let decoded = codec.decode_grid(&encoded).unwrap();
879
880 assert_eq!(decoded.num_cols(), 2);
881 assert_eq!(decoded.len(), 2);
882 assert_eq!(decoded.col_names().collect::<Vec<_>>(), vec!["dis", "area"]);
883 assert_eq!(
884 decoded.row(0).unwrap().get("dis"),
885 Some(&Kind::Str("Site One".into()))
886 );
887 assert_eq!(
888 decoded.row(1).unwrap().get("dis"),
889 Some(&Kind::Str("Site Two".into()))
890 );
891 }
892
893 #[test]
894 fn grid_with_meta_roundtrip() {
895 let codec = Json4Codec;
896
897 let mut meta = HDict::new();
898 meta.set("err", Kind::Marker);
899 meta.set("dis", Kind::Str("some error".into()));
900
901 let g = HGrid::from_parts(meta, vec![], vec![]);
902 let encoded = codec.encode_grid(&g).unwrap();
903 let decoded = codec.decode_grid(&encoded).unwrap();
904
905 assert!(decoded.is_err());
906 assert_eq!(
907 decoded.meta.get("dis"),
908 Some(&Kind::Str("some error".into()))
909 );
910 }
911
912 #[test]
913 fn grid_with_col_meta_roundtrip() {
914 let codec = Json4Codec;
915
916 let mut col_meta = HDict::new();
917 col_meta.set("unit", Kind::Str("kW".into()));
918
919 let cols = vec![HCol::new("name"), HCol::with_meta("power", col_meta)];
920 let g = HGrid::from_parts(HDict::new(), cols, vec![]);
921 let encoded = codec.encode_grid(&g).unwrap();
922 let decoded = codec.decode_grid(&encoded).unwrap();
923
924 assert_eq!(decoded.num_cols(), 2);
925 let power_col = decoded.col("power").unwrap();
926 assert_eq!(power_col.meta.get("unit"), Some(&Kind::Str("kW".into())));
927 }
928
929 #[test]
930 fn grid_with_missing_cells() {
931 let codec = Json4Codec;
932
933 let cols = vec![HCol::new("a"), HCol::new("b")];
934 let mut row1 = HDict::new();
935 row1.set("a", Kind::Number(Number::unitless(1.0)));
936 let g = HGrid::from_parts(HDict::new(), cols, vec![row1]);
939 let encoded = codec.encode_grid(&g).unwrap();
940 let decoded = codec.decode_grid(&encoded).unwrap();
941
942 let r = decoded.row(0).unwrap();
943 assert!(r.has("a"));
944 assert!(r.missing("b"));
945 }
946
947 #[test]
948 fn grid_nested_in_scalar() {
949 let codec = Json4Codec;
950
951 let cols = vec![HCol::new("x")];
952 let mut row = HDict::new();
953 row.set("x", Kind::Number(Number::unitless(42.0)));
954 let g = HGrid::from_parts(HDict::new(), cols, vec![row]);
955
956 let k = Kind::Grid(Box::new(g));
957 let encoded = codec.encode_scalar(&k).unwrap();
958 let decoded = codec.decode_scalar(&encoded).unwrap();
959
960 match decoded {
961 Kind::Grid(g) => {
962 assert_eq!(g.len(), 1);
963 assert_eq!(g.num_cols(), 1);
964 }
965 other => panic!("expected Grid, got {other:?}"),
966 }
967 }
968
969 #[test]
972 fn decode_plain_object_as_dict() {
973 let codec = Json4Codec;
974 let decoded = codec.decode_scalar(r#"{"a": 1, "b": "hello"}"#).unwrap();
975 match decoded {
976 Kind::Dict(d) => {
977 assert_eq!(d.get("a"), Some(&Kind::Number(Number::unitless(1.0))));
978 assert_eq!(d.get("b"), Some(&Kind::Str("hello".into())));
979 }
980 other => panic!("expected Dict, got {other:?}"),
981 }
982 }
983
984 #[test]
985 fn number_with_unit_encoding_format() {
986 let codec = Json4Codec;
987 let k = Kind::Number(Number::new(72.5, Some("\u{00B0}F".into())));
988 let encoded = codec.encode_scalar(&k).unwrap();
989 let val: Value = serde_json::from_str(&encoded).unwrap();
990 let obj = val.as_object().unwrap();
991 assert_eq!(obj.get("_kind").unwrap(), "number");
992 assert_eq!(obj.get("val").unwrap(), 72.5);
993 assert_eq!(obj.get("unit").unwrap(), "\u{00B0}F");
994 }
995
996 #[test]
997 fn number_inf_encoding_format() {
998 let codec = Json4Codec;
999 let k = Kind::Number(Number::unitless(f64::INFINITY));
1000 let encoded = codec.encode_scalar(&k).unwrap();
1001 let val: Value = serde_json::from_str(&encoded).unwrap();
1002 let obj = val.as_object().unwrap();
1003 assert_eq!(obj.get("val").unwrap(), "INF");
1004 }
1005
1006 #[test]
1007 fn number_nan_encoding_format() {
1008 let codec = Json4Codec;
1009 let k = Kind::Number(Number::unitless(f64::NAN));
1010 let encoded = codec.encode_scalar(&k).unwrap();
1011 let val: Value = serde_json::from_str(&encoded).unwrap();
1012 let obj = val.as_object().unwrap();
1013 assert_eq!(obj.get("val").unwrap(), "NaN");
1014 }
1015
1016 #[test]
1017 fn nested_dict_with_typed_values() {
1018 let mut inner = HDict::new();
1019 inner.set(
1020 "temp",
1021 Kind::Number(Number::new(72.5, Some("\u{00B0}F".into()))),
1022 );
1023 inner.set("site", Kind::Ref(HRef::from_val("s1")));
1024 let k = Kind::Dict(Box::new(inner));
1025 assert_eq!(roundtrip_scalar(k.clone()), k);
1026 }
1027
1028 #[test]
1029 fn list_with_all_types() {
1030 let offset = FixedOffset::west_opt(5 * 3600).unwrap();
1031 let dt = offset.with_ymd_and_hms(2024, 1, 1, 8, 0, 0).unwrap();
1032 let k = Kind::List(vec![
1033 Kind::Null,
1034 Kind::Marker,
1035 Kind::NA,
1036 Kind::Remove,
1037 Kind::Bool(true),
1038 Kind::Number(Number::new(42.0, Some("kW".into()))),
1039 Kind::Str("hello".into()),
1040 Kind::Ref(HRef::new("x", Some("Dis".into()))),
1041 Kind::Uri(Uri::new("http://a.com")),
1042 Kind::Symbol(Symbol::new("tag")),
1043 Kind::Date(NaiveDate::from_ymd_opt(2024, 1, 1).unwrap()),
1044 Kind::Time(NaiveTime::from_hms_opt(12, 0, 0).unwrap()),
1045 Kind::DateTime(HDateTime::new(dt, "New_York")),
1046 Kind::Coord(Coord::new(37.5, -77.4)),
1047 Kind::XStr(XStr::new("Color", "red")),
1048 ]);
1049 assert_eq!(roundtrip_scalar(k.clone()), k);
1050 }
1051}