Skip to content

Commit b951f74

Browse files
committed
feat: support serialize floating point numbers without trailing zeros
1 parent eedca4a commit b951f74

File tree

5 files changed

+41
-73
lines changed

5 files changed

+41
-73
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ jobs:
7575
fail-fast: false
7676
matrix:
7777
san: ["address,leak"]
78-
feature: ["", "arbitrary_precision", "sort_keys", "utf8_lossy"]
78+
feature: ["", "arbitrary_precision", "sort_keys", "utf8_lossy", "non_trailing_zero"]
7979
name: Sanitize ${{matrix.san}} feature ${{matrix.feature}}
8080
runs-on: [self-hosted, Linux, amd64]
8181
steps:

Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ license = "Apache-2.0"
1010
name = "sonic-rs"
1111
readme = "README.md"
1212
repository = "https://github.com/cloudwego/sonic-rs"
13-
version = "0.5.2"
13+
version = "0.5.3"
1414

1515
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
1616

@@ -54,3 +54,7 @@ utf8_lossy = []
5454

5555
# Enable sanitize, maybe cause 30% performance-loss in serialize.
5656
sanitize = []
57+
58+
# Serialize floating point numbers without trailing zeros if the float can be represented as an integer without loss of precision.
59+
# For example, `18.0` will be serialized as `18` instead of `18.0`.
60+
non_trailing_zero = []

scripts/test.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ cargo test --features sort_keys
1010

1111
cargo test --features utf8_lossy
1212

13+
cargo test --features non_trailing_zero
14+
1315
examples=$(cargo build --example 2>&1 | grep -v ":")
1416

1517
for example in $examples; do

src/format.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,11 @@ pub trait Formatter {
147147
where
148148
W: ?Sized + Write,
149149
{
150+
#[cfg(feature = "non_trailing_zero")]
151+
if value.fract() == 0.0 && value <= (i64::MAX as f32) && value >= (i64::MIN as f32) {
152+
return self.write_i64(writer, value as i64);
153+
}
154+
150155
let mut buffer = ryu::Buffer::new();
151156
let s = buffer.format_finite(value);
152157
writer.write_all(s.as_bytes())
@@ -158,6 +163,11 @@ pub trait Formatter {
158163
where
159164
W: ?Sized + Write,
160165
{
166+
#[cfg(feature = "non_trailing_zero")]
167+
if value.fract() == 0.0 && value <= (i64::MAX as f64) && value >= (i64::MIN as f64) {
168+
return self.write_i64(writer, value as i64);
169+
}
170+
161171
let mut buffer = ryu::Buffer::new();
162172
let s = buffer.format_finite(value);
163173
writer.write_all(s.as_bytes())

src/serde/mod.rs

Lines changed: 23 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -437,77 +437,6 @@ mod test {
437437
test_from_slice!(Data, &br#"{"content":[1,2,3,4,5]}"#[..]);
438438
}
439439

440-
use std::{
441-
fmt::{Formatter, Result as FmtResult},
442-
result::Result as StdResult,
443-
};
444-
445-
fn my_deseirlize_seq<'de, D>(deserializer: D) -> StdResult<(i64, i64), D::Error>
446-
where
447-
D: serde::de::Deserializer<'de>,
448-
{
449-
struct TupleVisitor;
450-
451-
impl<'de> serde::de::Visitor<'de> for TupleVisitor {
452-
type Value = (i64, i64);
453-
454-
fn expecting(&self, formatter: &mut Formatter) -> FmtResult {
455-
formatter.write_str("expect an array")
456-
}
457-
458-
fn visit_seq<S>(self, mut seq: S) -> StdResult<Self::Value, S::Error>
459-
where
460-
S: serde::de::SeqAccess<'de>,
461-
{
462-
let x = seq
463-
.next_element::<i64>()?
464-
.ok_or_else(|| serde::de::Error::invalid_length(0, &self))?;
465-
let y = seq
466-
.next_element::<i64>()?
467-
.ok_or_else(|| serde::de::Error::invalid_length(1, &self))?;
468-
Ok((x, y))
469-
}
470-
}
471-
472-
deserializer.deserialize_seq(TupleVisitor)
473-
}
474-
475-
fn my_deseirlize_map<'de, D>(deserializer: D) -> StdResult<(String, i64), D::Error>
476-
where
477-
D: serde::de::Deserializer<'de>,
478-
{
479-
struct MapVisitor;
480-
481-
impl<'de> serde::de::Visitor<'de> for MapVisitor {
482-
type Value = (String, i64);
483-
484-
fn expecting(&self, formatter: &mut Formatter) -> FmtResult {
485-
formatter.write_str("expect an array")
486-
}
487-
488-
fn visit_map<S>(self, mut map: S) -> StdResult<Self::Value, S::Error>
489-
where
490-
S: serde::de::MapAccess<'de>,
491-
{
492-
let x = map
493-
.next_key()?
494-
.ok_or_else(|| serde::de::Error::custom("miss a key"))?;
495-
let y = map.next_value::<i64>()?;
496-
Ok((x, y))
497-
}
498-
}
499-
500-
deserializer.deserialize_map(MapVisitor)
501-
}
502-
503-
#[derive(serde::Deserialize, Debug, Eq, PartialEq)]
504-
struct MyTuple {
505-
#[serde(deserialize_with = "my_deseirlize_seq")]
506-
seq: (i64, i64),
507-
#[serde(deserialize_with = "my_deseirlize_map")]
508-
map: (String, i64),
509-
}
510-
511440
#[test]
512441
fn test_serde_invalid_utf8() {
513442
let json = r#""王先生""#;
@@ -737,4 +666,27 @@ mod test {
737666
assert!(err.is_syntax());
738667
}
739668
}
669+
670+
#[test]
671+
fn test_serialize_float_non_trailing_zero() {
672+
use serde::Serialize;
673+
674+
#[derive(Serialize)]
675+
struct FloatTest {
676+
value: f64,
677+
}
678+
679+
let test = FloatTest { value: 18.0 };
680+
let json = to_string(&test).unwrap();
681+
682+
#[cfg(feature = "non_trailing_zero")]
683+
assert_eq!(json, r#"{"value":18}"#);
684+
685+
#[cfg(not(feature = "non_trailing_zero"))]
686+
assert_eq!(json, r#"{"value":18.0}"#);
687+
688+
let test = FloatTest { value: 18.1 };
689+
let json = to_string(&test).unwrap();
690+
assert_eq!(json, r#"{"value":18.1}"#);
691+
}
740692
}

0 commit comments

Comments
 (0)