json: Fix handling of NaN/Infinity
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, and reading null when specifically expecting a number should be interpreted as NaN. There's no way to round-trip Infinity.
This commit is contained in:
parent
bab614f5fa
commit
e1a9899a3a
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2012-2013 The Rust Project Developers. See the COPYRIGHT
|
||||
// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
|
@ -234,6 +234,7 @@ use std::fmt;
|
|||
use std::io::MemWriter;
|
||||
use std::io;
|
||||
use std::mem::{swap,transmute};
|
||||
use std::num::{FPNaN, FPInfinite};
|
||||
use std::num;
|
||||
use std::str::ScalarValue;
|
||||
use std::str;
|
||||
|
@ -349,6 +350,13 @@ fn escape_str(s: &str) -> String {
|
|||
escaped
|
||||
}
|
||||
|
||||
fn fmt_number_or_null(v: f64) -> String {
|
||||
match v.classify() {
|
||||
FPNaN | FPInfinite => String::from_str("null"),
|
||||
_ => f64::to_str_digits(v, 6u)
|
||||
}
|
||||
}
|
||||
|
||||
fn spaces(n: uint) -> String {
|
||||
String::from_char(n, ' ')
|
||||
}
|
||||
|
@ -412,7 +420,7 @@ impl<'a> ::Encoder<io::IoError> for Encoder<'a> {
|
|||
}
|
||||
|
||||
fn emit_f64(&mut self, v: f64) -> EncodeResult {
|
||||
write!(self.wr, "{}", f64::to_str_digits(v, 6u))
|
||||
write!(self.wr, "{}", fmt_number_or_null(v))
|
||||
}
|
||||
fn emit_f32(&mut self, v: f32) -> EncodeResult { self.emit_f64(v as f64) }
|
||||
|
||||
|
@ -608,7 +616,7 @@ impl<'a> ::Encoder<io::IoError> for PrettyEncoder<'a> {
|
|||
}
|
||||
|
||||
fn emit_f64(&mut self, v: f64) -> EncodeResult {
|
||||
write!(self.wr, "{}", f64::to_str_digits(v, 6u))
|
||||
write!(self.wr, "{}", fmt_number_or_null(v))
|
||||
}
|
||||
fn emit_f32(&mut self, v: f32) -> EncodeResult {
|
||||
self.emit_f64(v as f64)
|
||||
|
@ -1270,7 +1278,7 @@ impl<T: Iterator<char>> Parser<T> {
|
|||
'0' => {
|
||||
self.bump();
|
||||
|
||||
// There can be only one leading '0'.
|
||||
// A leading '0' must be the only digit before the decimal point.
|
||||
match self.ch_or_null() {
|
||||
'0' .. '9' => return self.error(InvalidNumber),
|
||||
_ => ()
|
||||
|
@ -1864,6 +1872,7 @@ impl ::Decoder<DecoderError> for Decoder {
|
|||
// is going to have a string here, as per JSON spec..
|
||||
Ok(FromStr::from_str(s.as_slice()).unwrap())
|
||||
},
|
||||
Null => Ok(f64::NAN),
|
||||
value => {
|
||||
Err(ExpectedError("Number".to_string(),
|
||||
format!("{}", value)))
|
||||
|
@ -2185,11 +2194,16 @@ impl ToJson for u64 {
|
|||
}
|
||||
|
||||
impl ToJson for f32 {
|
||||
fn to_json(&self) -> Json { Number(*self as f64) }
|
||||
fn to_json(&self) -> Json { (*self as f64).to_json() }
|
||||
}
|
||||
|
||||
impl ToJson for f64 {
|
||||
fn to_json(&self) -> Json { Number(*self) }
|
||||
fn to_json(&self) -> Json {
|
||||
match self.classify() {
|
||||
FPNaN | FPInfinite => Null,
|
||||
_ => Number(*self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToJson for () {
|
||||
|
@ -2282,6 +2296,8 @@ mod tests {
|
|||
InvalidSyntax, InvalidNumber, EOFWhileParsingObject, EOFWhileParsingList,
|
||||
EOFWhileParsingValue, EOFWhileParsingString, KeyMustBeAString, ExpectedColon,
|
||||
TrailingCharacters};
|
||||
use std::f32;
|
||||
use std::f64;
|
||||
use std::io;
|
||||
use std::collections::TreeMap;
|
||||
|
||||
|
@ -2335,6 +2351,15 @@ mod tests {
|
|||
|
||||
assert_eq!(Number(0.5).to_str().into_string(), "0.5".to_string());
|
||||
assert_eq!(Number(0.5).to_pretty_str().into_string(), "0.5".to_string());
|
||||
|
||||
assert_eq!(Number(f64::NAN).to_str().into_string(), "null".to_string());
|
||||
assert_eq!(Number(f64::NAN).to_pretty_str().into_string(), "null".to_string());
|
||||
|
||||
assert_eq!(Number(f64::INFINITY).to_str().into_string(), "null".to_string());
|
||||
assert_eq!(Number(f64::INFINITY).to_pretty_str().into_string(), "null".to_string());
|
||||
|
||||
assert_eq!(Number(f64::NEG_INFINITY).to_str().into_string(), "null".to_string());
|
||||
assert_eq!(Number(f64::NEG_INFINITY).to_pretty_str().into_string(), "null".to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -2583,6 +2608,7 @@ mod tests {
|
|||
fn test_read_number() {
|
||||
assert_eq!(from_str("+"), Err(SyntaxError(InvalidSyntax, 1, 1)));
|
||||
assert_eq!(from_str("."), Err(SyntaxError(InvalidSyntax, 1, 1)));
|
||||
assert_eq!(from_str("NaN"), Err(SyntaxError(InvalidSyntax, 1, 1)));
|
||||
assert_eq!(from_str("-"), Err(SyntaxError(InvalidNumber, 1, 2)));
|
||||
assert_eq!(from_str("00"), Err(SyntaxError(InvalidNumber, 1, 2)));
|
||||
assert_eq!(from_str("1."), Err(SyntaxError(InvalidNumber, 1, 3)));
|
||||
|
@ -2792,6 +2818,22 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[deriving(Decodable)]
|
||||
struct FloatStruct {
|
||||
f: f64,
|
||||
a: Vec<f64>
|
||||
}
|
||||
#[test]
|
||||
fn test_decode_struct_with_nan() {
|
||||
let encoded_str = "{\"f\":null,\"a\":[null,123]}";
|
||||
let json_object = from_str(encoded_str.as_slice());
|
||||
let mut decoder = Decoder::new(json_object.unwrap());
|
||||
let after: FloatStruct = Decodable::decode(&mut decoder).unwrap();
|
||||
assert!(after.f.is_nan());
|
||||
assert!(after.a.get(0).is_nan());
|
||||
assert_eq!(after.a.get(1), &123f64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_option() {
|
||||
let mut decoder = Decoder::new(from_str("null").unwrap());
|
||||
|
@ -2833,6 +2875,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[deriving(Decodable)]
|
||||
#[allow(dead_code)]
|
||||
struct DecodeStruct {
|
||||
x: f64,
|
||||
y: bool,
|
||||
|
@ -3362,6 +3405,8 @@ mod tests {
|
|||
assert_eq!(13.0_f32.to_json(), Number(13.0_f64));
|
||||
assert_eq!(14.0_f64.to_json(), Number(14.0_f64));
|
||||
assert_eq!(().to_json(), Null);
|
||||
assert_eq!(f32::INFINITY.to_json(), Null);
|
||||
assert_eq!(f64::NAN.to_json(), Null);
|
||||
assert_eq!(true.to_json(), Boolean(true));
|
||||
assert_eq!(false.to_json(), Boolean(false));
|
||||
assert_eq!("abc".to_string().to_json(), String("abc".to_string()));
|
||||
|
|
Loading…
Reference in New Issue