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_iter() {
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
204pub fn decode_kind(val: &Value) -> Result<Kind, CodecError> {
208 match val {
209 Value::Null => Ok(Kind::Null),
210 Value::Bool(b) => Ok(Kind::Bool(*b)),
211 Value::Number(n) => {
212 let v = n.as_f64().ok_or_else(|| CodecError::Parse {
214 pos: 0,
215 message: format!("cannot convert JSON number to f64: {n}"),
216 })?;
217 Ok(Kind::Number(Number::unitless(v)))
218 }
219 Value::String(s) => {
220 Ok(Kind::Str(s.clone()))
222 }
223 Value::Array(arr) => {
224 let items: Result<Vec<Kind>, CodecError> = arr.iter().map(decode_kind).collect();
225 Ok(Kind::List(items?))
226 }
227 Value::Object(m) => decode_object(m),
228 }
229}
230
231fn decode_object(m: &Map<String, Value>) -> Result<Kind, CodecError> {
233 match m.get("_kind") {
234 Some(Value::String(k)) => decode_typed(k, m),
235 _ => {
236 let mut dict = HDict::new();
238 for (key, val) in m {
239 dict.set(key.clone(), decode_kind(val)?);
240 }
241 Ok(Kind::Dict(Box::new(dict)))
242 }
243 }
244}
245
246fn decode_typed(kind: &str, m: &Map<String, Value>) -> Result<Kind, CodecError> {
248 match kind {
249 "marker" => Ok(Kind::Marker),
250 "na" => Ok(Kind::NA),
251 "remove" => Ok(Kind::Remove),
252 "number" => decode_number(m),
253 "ref" => decode_ref(m),
254 "uri" => {
255 let val = get_str(m, "val")?;
256 Ok(Kind::Uri(Uri::new(val)))
257 }
258 "symbol" => {
259 let val = get_str(m, "val")?;
260 Ok(Kind::Symbol(Symbol::new(val)))
261 }
262 "date" => {
263 let val = get_str(m, "val")?;
264 let d = NaiveDate::parse_from_str(&val, "%Y-%m-%d").map_err(|e| CodecError::Parse {
265 pos: 0,
266 message: format!("invalid date: {e}"),
267 })?;
268 Ok(Kind::Date(d))
269 }
270 "time" => {
271 let val = get_str(m, "val")?;
272 let t = parse_time(&val)?;
273 Ok(Kind::Time(t))
274 }
275 "dateTime" => decode_datetime(m),
276 "coord" => decode_coord(m),
277 "xstr" => {
278 let type_name = get_str(m, "type")?;
279 let val = get_str(m, "val")?;
280 Ok(Kind::XStr(XStr::new(type_name, val)))
281 }
282 "grid" => {
283 let grid = decode_grid_value(&Value::Object(m.clone()))?;
284 Ok(Kind::Grid(Box::new(grid)))
285 }
286 other => Err(CodecError::Parse {
287 pos: 0,
288 message: format!("unknown _kind: {other}"),
289 }),
290 }
291}
292
293fn decode_number(m: &Map<String, Value>) -> Result<Kind, CodecError> {
295 let val = m.get("val").ok_or_else(|| CodecError::Parse {
296 pos: 0,
297 message: "number missing 'val' field".into(),
298 })?;
299 let v = match val {
300 Value::Number(n) => n.as_f64().ok_or_else(|| CodecError::Parse {
301 pos: 0,
302 message: "cannot convert number val to f64".into(),
303 })?,
304 Value::String(s) => match s.as_str() {
305 "INF" => f64::INFINITY,
306 "-INF" => f64::NEG_INFINITY,
307 "NaN" => f64::NAN,
308 _ => s.parse::<f64>().map_err(|e| CodecError::Parse {
309 pos: 0,
310 message: format!("invalid number string: {e}"),
311 })?,
312 },
313 _ => {
314 return Err(CodecError::Parse {
315 pos: 0,
316 message: "number 'val' must be a number or string".into(),
317 });
318 }
319 };
320 let unit = match m.get("unit") {
321 Some(Value::String(u)) => Some(u.clone()),
322 _ => None,
323 };
324 Ok(Kind::Number(Number::new(v, unit)))
325}
326
327fn decode_ref(m: &Map<String, Value>) -> Result<Kind, CodecError> {
329 let val = get_str(m, "val")?;
330 let dis = match m.get("dis") {
331 Some(Value::String(d)) => Some(d.clone()),
332 _ => None,
333 };
334 Ok(Kind::Ref(HRef::new(val, dis)))
335}
336
337fn decode_datetime(m: &Map<String, Value>) -> Result<Kind, CodecError> {
339 let val = get_str(m, "val")?;
340 let dt = chrono::DateTime::parse_from_rfc3339(&val)
341 .or_else(|_| {
342 chrono::DateTime::parse_from_str(&val, "%Y-%m-%dT%H:%M:%S%:z")
344 })
345 .map_err(|e| CodecError::Parse {
346 pos: 0,
347 message: format!("invalid datetime: {e}"),
348 })?;
349 let tz = match m.get("tz") {
350 Some(Value::String(t)) => t.clone(),
351 _ => String::new(),
352 };
353 Ok(Kind::DateTime(HDateTime::new(dt, tz)))
354}
355
356fn decode_coord(m: &Map<String, Value>) -> Result<Kind, CodecError> {
358 let lat = get_f64(m, "lat")?;
359 let lng = get_f64(m, "lng")?;
360 Ok(Kind::Coord(Coord::new(lat, lng)))
361}
362
363pub fn decode_grid_value(val: &Value) -> Result<HGrid, CodecError> {
365 let m = match val {
366 Value::Object(m) => m,
367 _ => {
368 return Err(CodecError::Parse {
369 pos: 0,
370 message: "grid must be a JSON object".into(),
371 });
372 }
373 };
374
375 let meta = match m.get("meta") {
377 Some(Value::Object(meta_map)) => {
378 let mut dict = HDict::new();
379 for (key, val) in meta_map {
380 if key == "_kind" {
381 continue; }
383 dict.set(key.clone(), decode_kind(val)?);
384 }
385 dict
386 }
387 _ => HDict::new(),
388 };
389
390 let cols = match m.get("cols") {
392 Some(Value::Array(arr)) => {
393 let mut cols = Vec::with_capacity(arr.len());
394 for col_val in arr {
395 let col_obj = col_val.as_object().ok_or_else(|| CodecError::Parse {
396 pos: 0,
397 message: "col must be a JSON object".into(),
398 })?;
399 let name = get_str(col_obj, "name")?;
400 let col_meta = match col_obj.get("meta") {
401 Some(Value::Object(meta_map)) => {
402 let mut dict = HDict::new();
403 for (key, val) in meta_map {
404 dict.set(key.clone(), decode_kind(val)?);
405 }
406 dict
407 }
408 _ => HDict::new(),
409 };
410 cols.push(HCol::with_meta(name, col_meta));
411 }
412 cols
413 }
414 _ => Vec::new(),
415 };
416
417 let rows = match m.get("rows") {
419 Some(Value::Array(arr)) => {
420 let mut rows = Vec::with_capacity(arr.len());
421 for row_val in arr {
422 let row_obj = row_val.as_object().ok_or_else(|| CodecError::Parse {
423 pos: 0,
424 message: "row must be a JSON object".into(),
425 })?;
426 let mut dict = HDict::new();
427 for (key, val) in row_obj {
428 dict.set(key.clone(), decode_kind(val)?);
429 }
430 rows.push(dict);
431 }
432 rows
433 }
434 _ => Vec::new(),
435 };
436
437 Ok(HGrid::from_parts(meta, cols, rows))
438}
439
440fn parse_time(s: &str) -> Result<NaiveTime, CodecError> {
442 NaiveTime::parse_from_str(s, "%H:%M:%S%.f")
444 .or_else(|_| NaiveTime::parse_from_str(s, "%H:%M:%S"))
445 .map_err(|e| CodecError::Parse {
446 pos: 0,
447 message: format!("invalid time: {e}"),
448 })
449}
450
451fn get_str(m: &Map<String, Value>, key: &str) -> Result<String, CodecError> {
453 match m.get(key) {
454 Some(Value::String(s)) => Ok(s.clone()),
455 _ => Err(CodecError::Parse {
456 pos: 0,
457 message: format!("missing or invalid string field '{key}'"),
458 }),
459 }
460}
461
462fn get_f64(m: &Map<String, Value>, key: &str) -> Result<f64, CodecError> {
464 match m.get(key) {
465 Some(Value::Number(n)) => n.as_f64().ok_or_else(|| CodecError::Parse {
466 pos: 0,
467 message: format!("cannot convert '{key}' to f64"),
468 }),
469 _ => Err(CodecError::Parse {
470 pos: 0,
471 message: format!("missing or invalid number field '{key}'"),
472 }),
473 }
474}
475
476#[cfg(test)]
477mod tests {
478 use super::*;
479 use crate::data::{HCol, HDict, HGrid};
480 use chrono::{FixedOffset, NaiveDate, NaiveTime, TimeZone};
481
482 fn roundtrip_scalar(kind: Kind) -> Kind {
483 let codec = Json4Codec;
484 let encoded = codec.encode_scalar(&kind).unwrap();
485 codec.decode_scalar(&encoded).unwrap()
486 }
487
488 #[test]
491 fn null_roundtrip() {
492 assert_eq!(roundtrip_scalar(Kind::Null), Kind::Null);
493 }
494
495 #[test]
496 fn null_encodes_to_json_null() {
497 let codec = Json4Codec;
498 assert_eq!(codec.encode_scalar(&Kind::Null).unwrap(), "null");
499 }
500
501 #[test]
504 fn bool_true_roundtrip() {
505 assert_eq!(roundtrip_scalar(Kind::Bool(true)), Kind::Bool(true));
506 }
507
508 #[test]
509 fn bool_false_roundtrip() {
510 assert_eq!(roundtrip_scalar(Kind::Bool(false)), Kind::Bool(false));
511 }
512
513 #[test]
514 fn bool_encodes_to_json_bool() {
515 let codec = Json4Codec;
516 assert_eq!(codec.encode_scalar(&Kind::Bool(true)).unwrap(), "true");
517 assert_eq!(codec.encode_scalar(&Kind::Bool(false)).unwrap(), "false");
518 }
519
520 #[test]
523 fn marker_roundtrip() {
524 assert_eq!(roundtrip_scalar(Kind::Marker), Kind::Marker);
525 }
526
527 #[test]
528 fn marker_encodes_with_kind() {
529 let codec = Json4Codec;
530 let encoded = codec.encode_scalar(&Kind::Marker).unwrap();
531 assert!(encoded.contains("\"_kind\""));
532 assert!(encoded.contains("\"marker\""));
533 }
534
535 #[test]
538 fn na_roundtrip() {
539 assert_eq!(roundtrip_scalar(Kind::NA), Kind::NA);
540 }
541
542 #[test]
545 fn remove_roundtrip() {
546 assert_eq!(roundtrip_scalar(Kind::Remove), Kind::Remove);
547 }
548
549 #[test]
552 fn number_unitless_roundtrip() {
553 let k = Kind::Number(Number::unitless(72.5));
554 assert_eq!(roundtrip_scalar(k.clone()), k);
555 }
556
557 #[test]
558 fn number_with_unit_roundtrip() {
559 let k = Kind::Number(Number::new(72.5, Some("\u{00B0}F".into())));
560 assert_eq!(roundtrip_scalar(k.clone()), k);
561 }
562
563 #[test]
564 fn number_zero_roundtrip() {
565 let k = Kind::Number(Number::unitless(0.0));
566 assert_eq!(roundtrip_scalar(k.clone()), k);
567 }
568
569 #[test]
570 fn number_negative_roundtrip() {
571 let k = Kind::Number(Number::new(-23.45, Some("m\u{00B2}".into())));
572 assert_eq!(roundtrip_scalar(k.clone()), k);
573 }
574
575 #[test]
576 fn number_inf_roundtrip() {
577 let k = Kind::Number(Number::unitless(f64::INFINITY));
578 assert_eq!(roundtrip_scalar(k.clone()), k);
579 }
580
581 #[test]
582 fn number_neg_inf_roundtrip() {
583 let k = Kind::Number(Number::unitless(f64::NEG_INFINITY));
584 assert_eq!(roundtrip_scalar(k.clone()), k);
585 }
586
587 #[test]
588 fn number_nan_roundtrip() {
589 let codec = Json4Codec;
590 let k = Kind::Number(Number::unitless(f64::NAN));
591 let encoded = codec.encode_scalar(&k).unwrap();
592 let decoded = codec.decode_scalar(&encoded).unwrap();
593 match decoded {
594 Kind::Number(n) => {
595 assert!(n.val.is_nan());
596 assert_eq!(n.unit, None);
597 }
598 other => panic!("expected Number, got {other:?}"),
599 }
600 }
601
602 #[test]
603 fn number_integer_roundtrip() {
604 let k = Kind::Number(Number::unitless(42.0));
605 assert_eq!(roundtrip_scalar(k.clone()), k);
606 }
607
608 #[test]
609 fn plain_json_number_decodes_as_number() {
610 let codec = Json4Codec;
611 let decoded = codec.decode_scalar("42.5").unwrap();
612 assert_eq!(decoded, Kind::Number(Number::unitless(42.5)));
613 }
614
615 #[test]
616 fn plain_json_integer_decodes_as_number() {
617 let codec = Json4Codec;
618 let decoded = codec.decode_scalar("100").unwrap();
619 assert_eq!(decoded, Kind::Number(Number::unitless(100.0)));
620 }
621
622 #[test]
625 fn string_simple_roundtrip() {
626 let k = Kind::Str("hello".into());
627 assert_eq!(roundtrip_scalar(k.clone()), k);
628 }
629
630 #[test]
631 fn string_empty_roundtrip() {
632 let k = Kind::Str(String::new());
633 assert_eq!(roundtrip_scalar(k.clone()), k);
634 }
635
636 #[test]
637 fn string_with_special_chars_roundtrip() {
638 let k = Kind::Str("line1\nline2\ttab".into());
639 assert_eq!(roundtrip_scalar(k.clone()), k);
640 }
641
642 #[test]
643 fn string_encodes_as_plain_json_string() {
644 let codec = Json4Codec;
645 let encoded = codec.encode_scalar(&Kind::Str("hello".into())).unwrap();
646 assert_eq!(encoded, "\"hello\"");
647 }
648
649 #[test]
650 fn plain_json_string_decodes_as_str() {
651 let codec = Json4Codec;
652 let decoded = codec.decode_scalar("\"world\"").unwrap();
653 assert_eq!(decoded, Kind::Str("world".into()));
654 }
655
656 #[test]
659 fn ref_simple_roundtrip() {
660 let k = Kind::Ref(HRef::from_val("site-1"));
661 assert_eq!(roundtrip_scalar(k.clone()), k);
662 }
663
664 #[test]
665 fn ref_with_dis_roundtrip() {
666 let k = Kind::Ref(HRef::new("site-1", Some("Main Site".into())));
667 let rt = roundtrip_scalar(k.clone());
668 match rt {
669 Kind::Ref(r) => {
670 assert_eq!(r.val, "site-1");
671 assert_eq!(r.dis, Some("Main Site".into()));
672 }
673 other => panic!("expected Ref, got {other:?}"),
674 }
675 }
676
677 #[test]
680 fn uri_roundtrip() {
681 let k = Kind::Uri(Uri::new("http://example.com/api"));
682 assert_eq!(roundtrip_scalar(k.clone()), k);
683 }
684
685 #[test]
688 fn symbol_roundtrip() {
689 let k = Kind::Symbol(Symbol::new("hot-water"));
690 assert_eq!(roundtrip_scalar(k.clone()), k);
691 }
692
693 #[test]
696 fn date_roundtrip() {
697 let k = Kind::Date(NaiveDate::from_ymd_opt(2024, 3, 13).unwrap());
698 assert_eq!(roundtrip_scalar(k.clone()), k);
699 }
700
701 #[test]
704 fn time_roundtrip() {
705 let k = Kind::Time(NaiveTime::from_hms_opt(8, 12, 5).unwrap());
706 assert_eq!(roundtrip_scalar(k.clone()), k);
707 }
708
709 #[test]
710 fn time_with_frac_roundtrip() {
711 let k = Kind::Time(NaiveTime::from_hms_milli_opt(14, 30, 0, 123).unwrap());
712 assert_eq!(roundtrip_scalar(k.clone()), k);
713 }
714
715 #[test]
718 fn datetime_roundtrip() {
719 let offset = FixedOffset::west_opt(5 * 3600).unwrap();
720 let dt = offset.with_ymd_and_hms(2024, 1, 1, 8, 12, 5).unwrap();
721 let k = Kind::DateTime(HDateTime::new(dt, "New_York"));
722 assert_eq!(roundtrip_scalar(k.clone()), k);
723 }
724
725 #[test]
726 fn datetime_utc_roundtrip() {
727 let offset = FixedOffset::east_opt(0).unwrap();
728 let dt = offset.with_ymd_and_hms(2024, 6, 15, 12, 0, 0).unwrap();
729 let k = Kind::DateTime(HDateTime::new(dt, "UTC"));
730 assert_eq!(roundtrip_scalar(k.clone()), k);
731 }
732
733 #[test]
736 fn coord_roundtrip() {
737 let k = Kind::Coord(Coord::new(37.5458266, -77.4491888));
738 assert_eq!(roundtrip_scalar(k.clone()), k);
739 }
740
741 #[test]
744 fn xstr_roundtrip() {
745 let k = Kind::XStr(XStr::new("Color", "red"));
746 assert_eq!(roundtrip_scalar(k.clone()), k);
747 }
748
749 #[test]
752 fn list_empty_roundtrip() {
753 let k = Kind::List(vec![]);
754 assert_eq!(roundtrip_scalar(k.clone()), k);
755 }
756
757 #[test]
758 fn list_mixed_roundtrip() {
759 let k = Kind::List(vec![
760 Kind::Number(Number::unitless(1.0)),
761 Kind::Str("two".into()),
762 Kind::Marker,
763 Kind::Bool(true),
764 Kind::Null,
765 ]);
766 assert_eq!(roundtrip_scalar(k.clone()), k);
767 }
768
769 #[test]
770 fn list_nested_roundtrip() {
771 let k = Kind::List(vec![
772 Kind::List(vec![Kind::Number(Number::unitless(1.0))]),
773 Kind::List(vec![Kind::Str("inner".into())]),
774 ]);
775 assert_eq!(roundtrip_scalar(k.clone()), k);
776 }
777
778 #[test]
781 fn dict_empty_roundtrip() {
782 let k = Kind::Dict(Box::new(HDict::new()));
783 assert_eq!(roundtrip_scalar(k.clone()), k);
784 }
785
786 #[test]
787 fn dict_with_values_roundtrip() {
788 let mut d = HDict::new();
789 d.set("site", Kind::Marker);
790 d.set("dis", Kind::Str("Main".into()));
791 d.set(
792 "area",
793 Kind::Number(Number::new(4500.0, Some("ft\u{00B2}".into()))),
794 );
795 let k = Kind::Dict(Box::new(d));
796 assert_eq!(roundtrip_scalar(k.clone()), k);
797 }
798
799 #[test]
800 fn dict_no_kind_key() {
801 let codec = Json4Codec;
803 let mut d = HDict::new();
804 d.set("site", Kind::Marker);
805 let k = Kind::Dict(Box::new(d));
806 let encoded = codec.encode_scalar(&k).unwrap();
807 let val: Value = serde_json::from_str(&encoded).unwrap();
809 let obj = val.as_object().unwrap();
810 assert!(obj.get("_kind").is_none());
811 }
812
813 #[test]
816 fn grid_empty_roundtrip() {
817 let codec = Json4Codec;
818 let g = HGrid::new();
819 let encoded = codec.encode_grid(&g).unwrap();
820 let decoded = codec.decode_grid(&encoded).unwrap();
821 assert!(decoded.is_empty());
822 assert_eq!(decoded.num_cols(), 0);
823 }
824
825 #[test]
826 fn grid_with_data_roundtrip() {
827 let codec = Json4Codec;
828
829 let cols = vec![HCol::new("dis"), HCol::new("area")];
830 let mut row1 = HDict::new();
831 row1.set("dis", Kind::Str("Site One".into()));
832 row1.set("area", Kind::Number(Number::unitless(4500.0)));
833 let mut row2 = HDict::new();
834 row2.set("dis", Kind::Str("Site Two".into()));
835 row2.set("area", Kind::Number(Number::unitless(3200.0)));
836
837 let g = HGrid::from_parts(HDict::new(), cols, vec![row1, row2]);
838 let encoded = codec.encode_grid(&g).unwrap();
839 let decoded = codec.decode_grid(&encoded).unwrap();
840
841 assert_eq!(decoded.num_cols(), 2);
842 assert_eq!(decoded.len(), 2);
843 assert_eq!(decoded.col_names().collect::<Vec<_>>(), vec!["dis", "area"]);
844 assert_eq!(
845 decoded.row(0).unwrap().get("dis"),
846 Some(&Kind::Str("Site One".into()))
847 );
848 assert_eq!(
849 decoded.row(1).unwrap().get("dis"),
850 Some(&Kind::Str("Site Two".into()))
851 );
852 }
853
854 #[test]
855 fn grid_with_meta_roundtrip() {
856 let codec = Json4Codec;
857
858 let mut meta = HDict::new();
859 meta.set("err", Kind::Marker);
860 meta.set("dis", Kind::Str("some error".into()));
861
862 let g = HGrid::from_parts(meta, vec![], vec![]);
863 let encoded = codec.encode_grid(&g).unwrap();
864 let decoded = codec.decode_grid(&encoded).unwrap();
865
866 assert!(decoded.is_err());
867 assert_eq!(
868 decoded.meta.get("dis"),
869 Some(&Kind::Str("some error".into()))
870 );
871 }
872
873 #[test]
874 fn grid_with_col_meta_roundtrip() {
875 let codec = Json4Codec;
876
877 let mut col_meta = HDict::new();
878 col_meta.set("unit", Kind::Str("kW".into()));
879
880 let cols = vec![HCol::new("name"), HCol::with_meta("power", col_meta)];
881 let g = HGrid::from_parts(HDict::new(), cols, vec![]);
882 let encoded = codec.encode_grid(&g).unwrap();
883 let decoded = codec.decode_grid(&encoded).unwrap();
884
885 assert_eq!(decoded.num_cols(), 2);
886 let power_col = decoded.col("power").unwrap();
887 assert_eq!(power_col.meta.get("unit"), Some(&Kind::Str("kW".into())));
888 }
889
890 #[test]
891 fn grid_with_missing_cells() {
892 let codec = Json4Codec;
893
894 let cols = vec![HCol::new("a"), HCol::new("b")];
895 let mut row1 = HDict::new();
896 row1.set("a", Kind::Number(Number::unitless(1.0)));
897 let g = HGrid::from_parts(HDict::new(), cols, vec![row1]);
900 let encoded = codec.encode_grid(&g).unwrap();
901 let decoded = codec.decode_grid(&encoded).unwrap();
902
903 let r = decoded.row(0).unwrap();
904 assert!(r.has("a"));
905 assert!(r.missing("b"));
906 }
907
908 #[test]
909 fn grid_nested_in_scalar() {
910 let codec = Json4Codec;
911
912 let cols = vec![HCol::new("x")];
913 let mut row = HDict::new();
914 row.set("x", Kind::Number(Number::unitless(42.0)));
915 let g = HGrid::from_parts(HDict::new(), cols, vec![row]);
916
917 let k = Kind::Grid(Box::new(g));
918 let encoded = codec.encode_scalar(&k).unwrap();
919 let decoded = codec.decode_scalar(&encoded).unwrap();
920
921 match decoded {
922 Kind::Grid(g) => {
923 assert_eq!(g.len(), 1);
924 assert_eq!(g.num_cols(), 1);
925 }
926 other => panic!("expected Grid, got {other:?}"),
927 }
928 }
929
930 #[test]
933 fn decode_plain_object_as_dict() {
934 let codec = Json4Codec;
935 let decoded = codec.decode_scalar(r#"{"a": 1, "b": "hello"}"#).unwrap();
936 match decoded {
937 Kind::Dict(d) => {
938 assert_eq!(d.get("a"), Some(&Kind::Number(Number::unitless(1.0))));
939 assert_eq!(d.get("b"), Some(&Kind::Str("hello".into())));
940 }
941 other => panic!("expected Dict, got {other:?}"),
942 }
943 }
944
945 #[test]
946 fn number_with_unit_encoding_format() {
947 let codec = Json4Codec;
948 let k = Kind::Number(Number::new(72.5, Some("\u{00B0}F".into())));
949 let encoded = codec.encode_scalar(&k).unwrap();
950 let val: Value = serde_json::from_str(&encoded).unwrap();
951 let obj = val.as_object().unwrap();
952 assert_eq!(obj.get("_kind").unwrap(), "number");
953 assert_eq!(obj.get("val").unwrap(), 72.5);
954 assert_eq!(obj.get("unit").unwrap(), "\u{00B0}F");
955 }
956
957 #[test]
958 fn number_inf_encoding_format() {
959 let codec = Json4Codec;
960 let k = Kind::Number(Number::unitless(f64::INFINITY));
961 let encoded = codec.encode_scalar(&k).unwrap();
962 let val: Value = serde_json::from_str(&encoded).unwrap();
963 let obj = val.as_object().unwrap();
964 assert_eq!(obj.get("val").unwrap(), "INF");
965 }
966
967 #[test]
968 fn number_nan_encoding_format() {
969 let codec = Json4Codec;
970 let k = Kind::Number(Number::unitless(f64::NAN));
971 let encoded = codec.encode_scalar(&k).unwrap();
972 let val: Value = serde_json::from_str(&encoded).unwrap();
973 let obj = val.as_object().unwrap();
974 assert_eq!(obj.get("val").unwrap(), "NaN");
975 }
976
977 #[test]
978 fn nested_dict_with_typed_values() {
979 let mut inner = HDict::new();
980 inner.set(
981 "temp",
982 Kind::Number(Number::new(72.5, Some("\u{00B0}F".into()))),
983 );
984 inner.set("site", Kind::Ref(HRef::from_val("s1")));
985 let k = Kind::Dict(Box::new(inner));
986 assert_eq!(roundtrip_scalar(k.clone()), k);
987 }
988
989 #[test]
990 fn list_with_all_types() {
991 let offset = FixedOffset::west_opt(5 * 3600).unwrap();
992 let dt = offset.with_ymd_and_hms(2024, 1, 1, 8, 0, 0).unwrap();
993 let k = Kind::List(vec![
994 Kind::Null,
995 Kind::Marker,
996 Kind::NA,
997 Kind::Remove,
998 Kind::Bool(true),
999 Kind::Number(Number::new(42.0, Some("kW".into()))),
1000 Kind::Str("hello".into()),
1001 Kind::Ref(HRef::new("x", Some("Dis".into()))),
1002 Kind::Uri(Uri::new("http://a.com")),
1003 Kind::Symbol(Symbol::new("tag")),
1004 Kind::Date(NaiveDate::from_ymd_opt(2024, 1, 1).unwrap()),
1005 Kind::Time(NaiveTime::from_hms_opt(12, 0, 0).unwrap()),
1006 Kind::DateTime(HDateTime::new(dt, "New_York")),
1007 Kind::Coord(Coord::new(37.5, -77.4)),
1008 Kind::XStr(XStr::new("Color", "red")),
1009 ]);
1010 assert_eq!(roundtrip_scalar(k.clone()), k);
1011 }
1012}