about summary refs log tree commit diff
diff options
context:
space:
mode:
authormrec <mike.capp@gmail.com>2014-06-26 02:26:41 +0100
committermrec <mike.capp@gmail.com>2014-06-26 02:26:41 +0100
commite1a9899a3a12f9f5fde6d5757c13bc248e89617d (patch)
tree8663623d07a01cbeacb366494056a468c86e863b
parentbab614f5fa725d248afc5f0530c835f37998ce8f (diff)
downloadrust-e1a9899a3a12f9f5fde6d5757c13bc248e89617d.tar.gz
rust-e1a9899a3a12f9f5fde6d5757c13bc248e89617d.zip
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.
-rw-r--r--src/libserialize/json.rs57
1 files changed, 51 insertions, 6 deletions
diff --git a/src/libserialize/json.rs b/src/libserialize/json.rs
index 1721bd7ae15..9c91bd4b7e1 100644
--- a/src/libserialize/json.rs
+++ b/src/libserialize/json.rs
@@ -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()));