use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Value;
#[derive(Debug, PartialEq, Clone, Copy)]
pub(crate) struct F32WithNanInf(f32);
impl From<f32> for F32WithNanInf {
fn from(f: f32) -> Self {
F32WithNanInf(f)
}
}
impl From<F32WithNanInf> for f32 {
fn from(f: F32WithNanInf) -> f32 {
f.0
}
}
impl Serialize for F32WithNanInf {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serialize_f32_nan_inf_as_string(&self.0, serializer)
}
}
impl<'de> Deserialize<'de> for F32WithNanInf {
fn deserialize<D>(deserializer: D) -> Result<F32WithNanInf, D::Error>
where
D: Deserializer<'de>,
{
let f = deserialize_f32_nan_inf_from_string(deserializer)?;
Ok(F32WithNanInf(f))
}
}
fn serialize_f32_nan_inf_as_string<S>(value: &f32, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if value.is_nan() {
let bytes = value.to_le_bytes();
let hex_string = hex::encode(bytes);
let nan = format!("NaN|{}", &hex_string);
serializer.serialize_str(&nan)
} else if value.is_sign_positive() && value.is_infinite() {
let inf = "Inf";
serializer.serialize_str(inf)
} else if value.is_sign_negative() && value.is_infinite() {
let n_inf = "-Inf";
serializer.serialize_str(n_inf)
} else {
serializer.serialize_f32(*value)
}
}
fn deserialize_f32_nan_inf_from_string<'de, D>(deserializer: D) -> Result<f32, D::Error>
where
D: Deserializer<'de>,
{
let v = Value::deserialize(deserializer)?;
match v {
Value::String(s) => match s.to_lowercase().as_str() {
"inf" => Ok(f32::INFINITY),
"-inf" => Ok(f32::NEG_INFINITY),
other => {
if let Some(hex_string) = other.strip_prefix("nan|") {
let bytes = hex::decode(hex_string)
.map_err(|e| serde::de::Error::custom(e.to_string()))?;
let mut array = [0; 4];
array.copy_from_slice(&bytes);
let f = f32::from_le_bytes(array);
Ok(f)
} else {
Err(serde::de::Error::custom(format!(
r#"expected "NaN|########", "Inf" or "-Inf", found {other}"#
)))
}
}
},
Value::Number(n) => n
.as_f64()
.ok_or_else(|| serde::de::Error::custom("expected f64"))
.map(|f| f as f32),
other => Err(serde::de::Error::custom(format!(
r#"expected number, "NaN", "Inf" or "-Inf", found {other}"#
))),
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
use serde_json::json;
#[test]
fn test_f32_nan_inf() {
let f32_num = F32WithNanInf(1.0);
let f32_inf = F32WithNanInf(f32::INFINITY);
let f32_n_inf = F32WithNanInf(f32::NEG_INFINITY);
let f32_nan = F32WithNanInf(f32::NAN);
let f32_other_nan = F32WithNanInf(f32::from_le_bytes([0xff, 0xff, 0xff, 0xff]));
let json_num = json!(1.0);
let json_inf = json!("Inf");
let json_n_inf = json!("-Inf");
let json_nan = json!("NaN|0000c07f");
let json_other_nan = json!("NaN|ffffffff");
assert_eq!(serde_json::to_value(f32_num).unwrap(), json_num);
assert_eq!(serde_json::to_value(f32_inf).unwrap(), json_inf);
assert_eq!(serde_json::to_value(f32_n_inf).unwrap(), json_n_inf);
assert_eq!(serde_json::to_value(f32_nan).unwrap(), json_nan);
assert_eq!(serde_json::to_value(f32_other_nan).unwrap(), json_other_nan);
assert_eq!(
serde_json::from_value::<F32WithNanInf>(json_num).unwrap(),
f32_num
);
assert_eq!(
serde_json::from_value::<F32WithNanInf>(json_inf).unwrap(),
f32_inf
);
assert_eq!(
serde_json::from_value::<F32WithNanInf>(json_n_inf).unwrap(),
f32_n_inf
);
}
}