Skip to content

Commit f098ba7

Browse files
committed
feat(serde): add truncate-float-to-int feature for float to integer conversion
1 parent 751d15b commit f098ba7

4 files changed

Lines changed: 51 additions & 7 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ features = ["chrono", "bigint"]
1717
bigint = ["num-bigint", "num-traits"]
1818
default = ["chrono", "serde", "bigint"]
1919
serde = ["thiserror", "dep:serde"]
20+
truncate-float-to-int = []
2021

2122
[dependencies]
2223
anyhow = {version = "1"}

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ The crate supports the following features:
4747
- `chrono`: _(default enabled)._ chrono integration
4848
- adds a `JsValue::Date` variant that can be (de)serialized to/from a JS `Date`
4949
- `bigint`: _(default enabled)._ arbitrary precision integer support via [num-bigint](https://github.com/rust-num/num-bigint)
50+
- `truncate-float-to-int`: _(default disabled)._ when enabled, float values will be truncated to integers when converting to integer types. When disabled, only float with zero fractional part will be converted to integer types, otherwise an error will be returned.
5051

5152
## Installation
5253

src/serde/de.rs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -163,11 +163,23 @@ impl<'de> Deserializer<'de> {
163163

164164
let value = current.to_float()?;
165165

166-
if !value.is_finite() || value.fract() != 0.0 {
167-
return Err(Error::ExpectedInteger);
166+
#[cfg(feature = "truncate-float-to-int")]
167+
{
168+
if !value.is_finite() {
169+
return Err(Error::ExpectedInteger);
170+
}
171+
172+
Ok(value.trunc())
168173
}
169174

170-
Ok(value)
175+
#[cfg(not(feature = "truncate-float-to-int"))]
176+
{
177+
if !value.is_finite() || value.fract() != 0.0 {
178+
return Err(Error::ExpectedInteger);
179+
}
180+
181+
Ok(value)
182+
}
171183
}
172184

173185
fn parse_signed_integer(&self) -> Result<i64> {

tests/serde.rs

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -221,13 +221,15 @@ fn serde_ser_map() {
221221
assert!(json_str.contains("\"c\":3"));
222222
}
223223

224-
fn parse_from_js<T: serde::de::DeserializeOwned>(value: Value) -> T {
224+
fn parse_from_js_result<T: serde::de::DeserializeOwned>(value: Value) -> quickjs_rusty::serde::Result<T> {
225225
let context = Context::builder().build().unwrap();
226-
// use our to_js function to convert rust value to js value
227-
// now it is a js value in quickjs context
228226
let js_value = to_js(unsafe { context.context_raw() }, &value).unwrap();
229227

230-
match from_js::<T>(unsafe { context.context_raw() }, &js_value) {
228+
from_js::<T>(unsafe { context.context_raw() }, &js_value)
229+
}
230+
231+
fn parse_from_js<T: serde::de::DeserializeOwned>(value: Value) -> T {
232+
match parse_from_js_result(value) {
231233
Ok(v) => v,
232234
Err(err) => {
233235
panic!("{}", err);
@@ -294,6 +296,34 @@ fn serde_de_float_with_integer_value_to_i32() {
294296
assert_eq!(parse_from_js::<i32>(value), -1234);
295297
}
296298

299+
#[cfg(not(feature = "truncate-float-to-int"))]
300+
#[test]
301+
fn serde_de_float_with_fractional_value_to_u32_rejects() {
302+
let value = json!(1450.9);
303+
assert!(parse_from_js_result::<u32>(value).is_err());
304+
}
305+
306+
#[cfg(not(feature = "truncate-float-to-int"))]
307+
#[test]
308+
fn serde_de_float_with_fractional_value_to_i32_rejects() {
309+
let value = json!(-1234.9);
310+
assert!(parse_from_js_result::<i32>(value).is_err());
311+
}
312+
313+
#[cfg(feature = "truncate-float-to-int")]
314+
#[test]
315+
fn serde_de_float_with_fractional_value_to_u32_truncates() {
316+
let value = json!(1450.9);
317+
assert_eq!(parse_from_js::<u32>(value), 1450);
318+
}
319+
320+
#[cfg(feature = "truncate-float-to-int")]
321+
#[test]
322+
fn serde_de_float_with_fractional_value_to_i32_truncates() {
323+
let value = json!(-1234.9);
324+
assert_eq!(parse_from_js::<i32>(value), -1234);
325+
}
326+
297327
#[test]
298328
fn serde_de_option_none() {
299329
let value = json!(None::<()>);

0 commit comments

Comments
 (0)