Skip to content

Commit fa63b8c

Browse files
committed
auto merge of #15192 : mrec/rust/json-nan, r=alexcrichton
The JSON spec requires that these special values be serialized as "null"; the current serialization breaks any conformant JSON parser. So encoding needs to output "null", `to_json` on floating-point types can return `Null` as well as `Number` values, and reading a `Null` value when specifically expecting a number should be interpreted as NaN. There's no way to round-trip Infinity through JSON. This is my first attempt at both writing Rust and opening pull requests, so please dial your derp detector up to eleven when reviewing. A `rustc --test lib.rs` in `libserialize` passes all tests; a `make check` of the whole tree fails with the error below, but it doesn't look obviously related and the docs say that `make check` is known to be flaky on Windows. ---- [compile-fail] compile-fail/svh-change-significant-cfg.rs stdout ---- task '[compile-fail] compile-fail/svh-change-significant-cfg.rs' failed at 'called `Result:: unwrap()` on an `Err` value: couldn't create file (end of file (unknown error); path=i686-pc-mingw32 \test\compile-fail\svh-a-base.err; mode=truncate; access=write)', C:\msys\home\Mike\rust\src\libcore \result.rs:545 Incidentally, it may just be my lack of familiarity with the language and its idioms, but the duplication between `Encoder`/`PrettyEncoder` had a distinct code smell to it. The size of the file (~3500 lines) also made it a bit hard to navigate. Has there been any discussion of refactoring and/or breaking it up? I couldn't find anything in Issues except the ancient #9028.
2 parents fc502e2 + e1a9899 commit fa63b8c

File tree

1 file changed

+51
-6
lines changed

1 file changed

+51
-6
lines changed

src/libserialize/json.rs

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2012-2013 The Rust Project Developers. See the COPYRIGHT
1+
// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
22
// file at the top-level directory of this distribution and at
33
// http://rust-lang.org/COPYRIGHT.
44
//
@@ -234,6 +234,7 @@ use std::fmt;
234234
use std::io::MemWriter;
235235
use std::io;
236236
use std::mem::{swap,transmute};
237+
use std::num::{FPNaN, FPInfinite};
237238
use std::num;
238239
use std::str::ScalarValue;
239240
use std::str;
@@ -349,6 +350,13 @@ fn escape_str(s: &str) -> String {
349350
escaped
350351
}
351352

353+
fn fmt_number_or_null(v: f64) -> String {
354+
match v.classify() {
355+
FPNaN | FPInfinite => String::from_str("null"),
356+
_ => f64::to_str_digits(v, 6u)
357+
}
358+
}
359+
352360
fn spaces(n: uint) -> String {
353361
String::from_char(n, ' ')
354362
}
@@ -412,7 +420,7 @@ impl<'a> ::Encoder<io::IoError> for Encoder<'a> {
412420
}
413421

414422
fn emit_f64(&mut self, v: f64) -> EncodeResult {
415-
write!(self.wr, "{}", f64::to_str_digits(v, 6u))
423+
write!(self.wr, "{}", fmt_number_or_null(v))
416424
}
417425
fn emit_f32(&mut self, v: f32) -> EncodeResult { self.emit_f64(v as f64) }
418426

@@ -608,7 +616,7 @@ impl<'a> ::Encoder<io::IoError> for PrettyEncoder<'a> {
608616
}
609617

610618
fn emit_f64(&mut self, v: f64) -> EncodeResult {
611-
write!(self.wr, "{}", f64::to_str_digits(v, 6u))
619+
write!(self.wr, "{}", fmt_number_or_null(v))
612620
}
613621
fn emit_f32(&mut self, v: f32) -> EncodeResult {
614622
self.emit_f64(v as f64)
@@ -1270,7 +1278,7 @@ impl<T: Iterator<char>> Parser<T> {
12701278
'0' => {
12711279
self.bump();
12721280

1273-
// There can be only one leading '0'.
1281+
// A leading '0' must be the only digit before the decimal point.
12741282
match self.ch_or_null() {
12751283
'0' .. '9' => return self.error(InvalidNumber),
12761284
_ => ()
@@ -1864,6 +1872,7 @@ impl ::Decoder<DecoderError> for Decoder {
18641872
// is going to have a string here, as per JSON spec..
18651873
Ok(FromStr::from_str(s.as_slice()).unwrap())
18661874
},
1875+
Null => Ok(f64::NAN),
18671876
value => {
18681877
Err(ExpectedError("Number".to_string(),
18691878
format!("{}", value)))
@@ -2185,11 +2194,16 @@ impl ToJson for u64 {
21852194
}
21862195

21872196
impl ToJson for f32 {
2188-
fn to_json(&self) -> Json { Number(*self as f64) }
2197+
fn to_json(&self) -> Json { (*self as f64).to_json() }
21892198
}
21902199

21912200
impl ToJson for f64 {
2192-
fn to_json(&self) -> Json { Number(*self) }
2201+
fn to_json(&self) -> Json {
2202+
match self.classify() {
2203+
FPNaN | FPInfinite => Null,
2204+
_ => Number(*self)
2205+
}
2206+
}
21932207
}
21942208

21952209
impl ToJson for () {
@@ -2282,6 +2296,8 @@ mod tests {
22822296
InvalidSyntax, InvalidNumber, EOFWhileParsingObject, EOFWhileParsingList,
22832297
EOFWhileParsingValue, EOFWhileParsingString, KeyMustBeAString, ExpectedColon,
22842298
TrailingCharacters};
2299+
use std::f32;
2300+
use std::f64;
22852301
use std::io;
22862302
use std::collections::TreeMap;
22872303

@@ -2335,6 +2351,15 @@ mod tests {
23352351

23362352
assert_eq!(Number(0.5).to_str().into_string(), "0.5".to_string());
23372353
assert_eq!(Number(0.5).to_pretty_str().into_string(), "0.5".to_string());
2354+
2355+
assert_eq!(Number(f64::NAN).to_str().into_string(), "null".to_string());
2356+
assert_eq!(Number(f64::NAN).to_pretty_str().into_string(), "null".to_string());
2357+
2358+
assert_eq!(Number(f64::INFINITY).to_str().into_string(), "null".to_string());
2359+
assert_eq!(Number(f64::INFINITY).to_pretty_str().into_string(), "null".to_string());
2360+
2361+
assert_eq!(Number(f64::NEG_INFINITY).to_str().into_string(), "null".to_string());
2362+
assert_eq!(Number(f64::NEG_INFINITY).to_pretty_str().into_string(), "null".to_string());
23382363
}
23392364

23402365
#[test]
@@ -2583,6 +2608,7 @@ mod tests {
25832608
fn test_read_number() {
25842609
assert_eq!(from_str("+"), Err(SyntaxError(InvalidSyntax, 1, 1)));
25852610
assert_eq!(from_str("."), Err(SyntaxError(InvalidSyntax, 1, 1)));
2611+
assert_eq!(from_str("NaN"), Err(SyntaxError(InvalidSyntax, 1, 1)));
25862612
assert_eq!(from_str("-"), Err(SyntaxError(InvalidNumber, 1, 2)));
25872613
assert_eq!(from_str("00"), Err(SyntaxError(InvalidNumber, 1, 2)));
25882614
assert_eq!(from_str("1."), Err(SyntaxError(InvalidNumber, 1, 3)));
@@ -2792,6 +2818,22 @@ mod tests {
27922818
);
27932819
}
27942820

2821+
#[deriving(Decodable)]
2822+
struct FloatStruct {
2823+
f: f64,
2824+
a: Vec<f64>
2825+
}
2826+
#[test]
2827+
fn test_decode_struct_with_nan() {
2828+
let encoded_str = "{\"f\":null,\"a\":[null,123]}";
2829+
let json_object = from_str(encoded_str.as_slice());
2830+
let mut decoder = Decoder::new(json_object.unwrap());
2831+
let after: FloatStruct = Decodable::decode(&mut decoder).unwrap();
2832+
assert!(after.f.is_nan());
2833+
assert!(after.a.get(0).is_nan());
2834+
assert_eq!(after.a.get(1), &123f64);
2835+
}
2836+
27952837
#[test]
27962838
fn test_decode_option() {
27972839
let mut decoder = Decoder::new(from_str("null").unwrap());
@@ -2833,6 +2875,7 @@ mod tests {
28332875
}
28342876

28352877
#[deriving(Decodable)]
2878+
#[allow(dead_code)]
28362879
struct DecodeStruct {
28372880
x: f64,
28382881
y: bool,
@@ -3362,6 +3405,8 @@ mod tests {
33623405
assert_eq!(13.0_f32.to_json(), Number(13.0_f64));
33633406
assert_eq!(14.0_f64.to_json(), Number(14.0_f64));
33643407
assert_eq!(().to_json(), Null);
3408+
assert_eq!(f32::INFINITY.to_json(), Null);
3409+
assert_eq!(f64::NAN.to_json(), Null);
33653410
assert_eq!(true.to_json(), Boolean(true));
33663411
assert_eq!(false.to_json(), Boolean(false));
33673412
assert_eq!("abc".to_string().to_json(), String("abc".to_string()));

0 commit comments

Comments
 (0)