Repository: not-fl3/nanoserde
Pull request #106 (2024-06-18 03:35:04)

fix ron & json serialization of large floats
Currently for JSON and RON, nanoserde serializes large floats as integers when possible, and during deserialization first reads them as integers and later converts to a float. This leads to a bug when handling large numbers. For example, 1e30 is serialized as "1000000000000000000000000000000", which attempts to be deserialized as a u64 and errors with a positive overflow.

This PR changes float serialization to use the debug trait rather than the display trait, resulting in the representation "1e30" which is deserialized directly to a float.

knickish: Thanks for doing this, looks like a win all the way around

Patch:
diff --git a/src/serde_json.rs b/src/serde_json.rs
index 47ceef0..4c42ec3 100644
--- a/src/serde_json.rs
+++ b/src/serde_json.rs
@@ -715,7 +715,7 @@ macro_rules! impl_ser_de_json_float {
     ( $ ty: ident) => {
         impl SerJson for $ty {
             fn ser_json(&self, _d: usize, s: &mut SerJsonState) {
-                s.out.push_str(&self.to_string());
+                s.out.push_str(&format!("{self:?}"));
             }
         }
 
diff --git a/src/serde_ron.rs b/src/serde_ron.rs
index ce8b954..71077e7 100644
--- a/src/serde_ron.rs
+++ b/src/serde_ron.rs
@@ -726,7 +726,7 @@ macro_rules! impl_ser_de_ron_float {
     ( $ ty: ident) => {
         impl SerRon for $ty {
             fn ser_ron(&self, _d: usize, s: &mut SerRonState) {
-                s.out.push_str(&self.to_string());
+                s.out.push_str(&format!("{self:?}"));
             }
         }
 


Test patch:
diff --git a/tests/json.rs b/tests/json.rs
index 5aa14d1..dc160f8 100644
--- a/tests/json.rs
+++ b/tests/json.rs
@@ -3,6 +3,7 @@ use nanoserde::{DeJson, SerJson};
 
 use std::{
     collections::{BTreeMap, BTreeSet, LinkedList},
+    fmt::Debug,
     sync::atomic::AtomicBool,
 };
 
@@ -702,6 +703,41 @@ fn test_various_escapes() {
     assert_eq!(unescaped, "\n\t\u{20}\x0c\x08\\\"/😋\r");
 }
 
+#[test]
+fn test_various_floats() {
+    #[derive(Debug, SerJson, DeJson, PartialEq)]
+    struct FloatWrapper {
+        f32: f32,
+        f64: f64,
+    }
+
+    impl From<&(f32, f64)> for FloatWrapper {
+        fn from(value: &(f32, f64)) -> Self {
+            Self {
+                f32: value.0,
+                f64: value.1,
+            }
+        }
+    }
+
+    let cases: &[(f32, f64)] = &[
+        (0.0, 0.0),
+        (f32::MAX, f64::MAX),
+        (f32::MIN, f64::MIN),
+        (f32::MIN_POSITIVE, f64::MIN_POSITIVE),
+    ];
+
+    for case in cases {
+        assert_eq!(
+            FloatWrapper::from(case),
+            <FloatWrapper as DeJson>::deserialize_json(&dbg!(
+                FloatWrapper::from(case).serialize_json()
+            ))
+            .unwrap()
+        )
+    }
+}
+
 // there are only 1024*1024 surrogate pairs, so we can do an exhautive test.
 #[test]
 #[cfg_attr(miri, ignore)]
diff --git a/tests/ron.rs b/tests/ron.rs
index df8395e..b87e462 100644
--- a/tests/ron.rs
+++ b/tests/ron.rs
@@ -450,6 +450,41 @@ fn test_various_escapes() {
     assert_eq!(unescaped, "\n\t\u{20}\x0c\x08\\\"/😋\r");
 }
 
+#[test]
+fn test_various_floats() {
+    #[derive(Debug, SerRon, DeRon, PartialEq)]
+    struct FloatWrapper {
+        f32: f32,
+        f64: f64,
+    }
+
+    impl From<&(f32, f64)> for FloatWrapper {
+        fn from(value: &(f32, f64)) -> Self {
+            Self {
+                f32: value.0,
+                f64: value.1,
+            }
+        }
+    }
+
+    let cases: &[(f32, f64)] = &[
+        (0.0, 0.0),
+        (f32::MAX, f64::MAX),
+        (f32::MIN, f64::MIN),
+        (f32::MIN_POSITIVE, f64::MIN_POSITIVE),
+    ];
+
+    for case in cases {
+        assert_eq!(
+            FloatWrapper::from(case),
+            <FloatWrapper as DeRon>::deserialize_ron(&dbg!(
+                FloatWrapper::from(case).serialize_ron()
+            ))
+            .unwrap()
+        )
+    }
+}
+
 // there are only 1024*1024 surrogate pairs, so we can do an exhautive test.
 #[test]
 #[cfg_attr(miri, ignore)]
diff --git a/tests/ser_de.rs b/tests/ser_de.rs
index 0509b0c..48bf3e3 100644
--- a/tests/ser_de.rs
+++ b/tests/ser_de.rs
@@ -25,6 +25,7 @@ fn ser_de() {
         e: Option<BTreeMap<String, String>>,
         f: Option<([u32; 4], String)>,
         g: (),
+        h: f64,
     }
 
     let mut map = BTreeMap::new();
@@ -32,12 +33,13 @@ fn ser_de() {
 
     let test: Test = Test {
         a: 1,
-        b: 2.,
+        b: 2.718281828459045,
         c: Some("asd".to_string()),
         d: None,
         e: Some(map),
         f: Some(([1, 2, 3, 4], "tuple".to_string())),
         g: (),
+        h: 1e30,
     };
 
     #[cfg(feature = "binary")]