My goal is to take a serde_json::Value::Number
and if the number it contains is represented as a non-integral value with a fractional part of 0.0
, convert it into a serde_json::Value::Number
containing the equivalent integer representation.
I have the following snippet that attempts this:
use serde_json::{Number, Value};
fn trim_trailing_zeros(val: Value) -> Value {
match val {
Value::Number(n) => Value::Number({
match n.as_f64() {
Some(m) => {
if m.fract() == 0.0 {
Number::from(m as i64)
} else {
n
}
}
_ => n,
}
}),
var => var,
}
}
The problem with this is if the number can't be accurately converted to an f64
as this Rust Playground link shows, the output is not equal to the input. This seems insurmountable because the field of a Number
is private, so I can only use these few methods, and one that seems unavoidable is as_f64
. The others seem to not be of use at all in this problem.
Can anyone think of another way to do this? Is there some way I can convert a Value::Number
into a string and try to parse it without loss of accuracy?
Edit: As pointed out in the comments, first checking that the Number
contains an i64
or f64
with is_i64()
or is_f64()
before attempting an as_f64()
also could theoretically run into conversion issues.
One way to achieve this is using the arbitrary_precision
feature of serde_json
. That feature stores the number as a string, and then you can do the parsing yourself:
[dependencies]
serde_json = { version = "*", features = ["arbitrary_precision"] }
Value::Number(n) => Value::Number({
if let Some((whole, fract)) = n.as_str().split_once('.') {
if fract.as_bytes().iter().all(|&b| b == b'0') {
let i: i64 = whole.parse().unwrap();
Number::from(i)
} else {
n
}
} else {
let i: i64 = n.as_str().parse().unwrap();
Number::from(i)
}
}),