#![forbid(unsafe_code)]
use sonic_rs::{JsonNumberTrait, JsonValueMutTrait, JsonValueTrait, Number, Value};
#[must_use]
pub fn finite_or_null(value: f64) -> Value {
Number::from_f64(value).map_or_else(Value::new_null, Value::from)
}
pub fn canonicalize(value: &mut Value) {
if let Some(arr) = value.as_array_mut() {
for item in arr.iter_mut() {
canonicalize(item);
}
return;
}
if let Some(obj) = value.as_object_mut() {
for (_, v) in obj.iter_mut() {
canonicalize(v);
}
return;
}
if let Some(num) = value.as_number() {
if let Some(f) = num.as_f64() {
if !f.is_finite() {
*value = Value::new_null();
}
}
}
}
pub fn canonicalize_and_serialize(value: &mut Value) -> Result<String, sonic_rs::Error> {
canonicalize(value);
sonic_rs::to_string(&*value)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn nan_becomes_null() {
let v = finite_or_null(f64::NAN);
assert!(v.is_null(), "NaN must canonicalise to null, got {v:?}");
}
#[test]
fn pos_inf_becomes_null() {
let v = finite_or_null(f64::INFINITY);
assert!(v.is_null(), "+Inf must canonicalise to null, got {v:?}");
}
#[test]
fn neg_inf_becomes_null() {
let v = finite_or_null(f64::NEG_INFINITY);
assert!(v.is_null(), "-Inf must canonicalise to null, got {v:?}");
}
#[test]
fn finite_passthrough_42_5() {
let v = finite_or_null(42.5);
assert_eq!(v.as_f64(), Some(42.5));
}
#[test]
fn finite_zero_passthrough() {
let v = finite_or_null(0.0);
assert_eq!(v.as_f64(), Some(0.0));
}
#[test]
fn finite_negative_passthrough() {
let v = finite_or_null(-1234.5678);
assert_eq!(v.as_f64(), Some(-1234.5678));
}
#[test]
fn canonicalize_walks_arrays() {
let mut arr = sonic_rs::array![
sonic_rs::to_value(&1.0_f64).expect("finite ok"),
finite_or_null(f64::NAN),
sonic_rs::to_value(&3.0_f64).expect("finite ok"),
]
.into_value();
canonicalize(&mut arr);
let s = sonic_rs::to_string(&arr).expect("serialises after canonicalise");
assert_eq!(s, "[1.0,null,3.0]");
}
#[test]
fn canonicalize_walks_objects() {
let mut obj = sonic_rs::json!({
"ok": 1.5_f64,
"bad": Value::new_null(),
"nested": {
"deep": Value::new_null(),
}
});
if let Some(o) = obj.as_object_mut() {
o.insert(&"bad", finite_or_null(f64::NAN));
if let Some(nested) = o.get_mut(&"nested").and_then(|v| v.as_object_mut()) {
nested.insert(&"deep", finite_or_null(f64::INFINITY));
}
}
canonicalize(&mut obj);
let s = sonic_rs::to_string(&obj).expect("serialises");
assert!(s.contains("\"bad\":null"), "got {s}");
assert!(s.contains("\"deep\":null"), "got {s}");
assert!(s.contains("\"ok\":1.5"), "got {s}");
}
#[test]
fn canonicalize_and_serialize_succeeds_after_nan() {
let mut v = sonic_rs::array![
finite_or_null(f64::NAN),
sonic_rs::to_value(&2.0_f64).expect("finite"),
]
.into_value();
let s = canonicalize_and_serialize(&mut v).expect("must serialise");
assert_eq!(s, "[null,2.0]");
}
}