diff --git a/Cargo.toml b/Cargo.toml index ff591a4..acc06e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "json" -version = "0.7.1" +version = "0.7.2" authors = ["Maciej Hirsz "] description = "JSON implementation in Rust" repository = "https://github.com/maciejhirsz/json-rust" diff --git a/README.md b/README.md index 9d6142e..2d8b5a9 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,9 @@ Parse and serialize [JSON](http://json.org/) with ease. -**[Complete Documentation](http://terhix.com/doc/json/) - [Cargo](https://crates.io/crates/json) - [Repository](https://github.com/maciejhirsz/json-rust)** +**[Complete Documentation](http://terhix.com/doc/json/) -** +**[Cargo](https://crates.io/crates/json) -** +**[Repository](https://github.com/maciejhirsz/json-rust)** ## Why? diff --git a/src/codegen.rs b/src/codegen.rs index 53e3438..53e2ddd 100644 --- a/src/codegen.rs +++ b/src/codegen.rs @@ -36,8 +36,8 @@ pub trait Generator { fn write_digits_from_u64(&mut self, mut num: u64, length: &mut u8) { let digit = (num % 10) as u8; - num /= 10; - if num > 0 { + if num > 9 { + num /= 10; self.write_digits_from_u64(num, length); } *length += 1; @@ -54,7 +54,7 @@ pub trait Generator { self.write_digits_from_u64(num as u64, &mut length); let mut fract = num.fract(); - if fract < 1e-10 { + if fract < 1e-16 { return; } @@ -64,7 +64,7 @@ pub trait Generator { fract = fract.fract(); length += 2; - while length < 17 && fract > 0.01 { + while length < 17 && fract > 1e-15 { fract *= 10.0; self.write_char((fract as u8) + b'0'); fract = fract.fract(); diff --git a/src/lib.rs b/src/lib.rs index 3d1fa88..456860d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,9 @@ //! //! Parse and serialize [JSON](http://json.org/) with ease. //! -//! **[Complete Documentation](http://terhix.com/doc/json/) - [Cargo](https://crates.io/crates/json) - [Repository](https://github.com/maciejhirsz/json-rust)** +//! **[Complete Documentation](http://terhix.com/doc/json/) -** +//! **[Cargo](https://crates.io/crates/json) -** +//! **[Repository](https://github.com/maciejhirsz/json-rust)** //! //! ## Why? //! @@ -244,6 +246,11 @@ impl fmt::Display for JsonValue { } } +/// Convenience for `JsonValue::from(value)` +pub fn from(value: T) -> JsonValue where T: Into { + value.into() +} + #[deprecated(since="0.5.0", note="Use `value.dump(0)` instead")] pub fn stringify_ref(root: &JsonValue) -> String { root.dump() @@ -262,6 +269,7 @@ pub fn stringify_pretty(root: T, spaces: u16) -> String where T: Into ($crate::JsonValue::new_array()); @@ -307,18 +315,20 @@ macro_rules! implement_extras { impl From> for JsonValue { fn from(mut val: Vec<$from>) -> JsonValue { - JsonValue::Array(val.drain(..) - .map(|value| value.into()) - .collect::>() + JsonValue::Array( + val.drain(..) + .map(|value| value.into()) + .collect() ) } } impl From>> for JsonValue { fn from(mut val: Vec>) -> JsonValue { - JsonValue::Array(val.drain(..) - .map(|item| item.into()) - .collect::>() + JsonValue::Array( + val.drain(..) + .map(|item| item.into()) + .collect() ) } } diff --git a/src/parser.rs b/src/parser.rs index 249963c..c164840 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -19,12 +19,14 @@ pub enum Token { } macro_rules! expect_char { - ($tok:ident, $ch:pat) => { - match $tok.source.next() { - Some($ch) => {}, - Some(ch) => return Err(JsonError::unexpected_character(ch)), - None => return Err(JsonError::UnexpectedEndOfJson) - } + ($tok:ident, $( $ch:pat ),*) => { + $( + match $tok.source.next() { + Some($ch) => {}, + Some(ch) => return Err(JsonError::unexpected_character(ch)), + None => return Err(JsonError::UnexpectedEndOfJson) + } + )* } } @@ -205,22 +207,15 @@ impl<'a> Tokenizer<'a> { b'"' => Token::String(try!(self.read_string())), b'0' ... b'9' | b'-' => Token::Number(try!(self.read_number(ch))), b't' => { - expect_char!(self, b'r'); - expect_char!(self, b'u'); - expect_char!(self, b'e'); + expect_char!(self, b'r', b'u', b'e'); Token::Boolean(true) }, b'f' => { - expect_char!(self, b'a'); - expect_char!(self, b'l'); - expect_char!(self, b's'); - expect_char!(self, b'e'); + expect_char!(self, b'a', b'l', b's', b'e'); Token::Boolean(false) }, b'n' => { - expect_char!(self, b'u'); - expect_char!(self, b'l'); - expect_char!(self, b'l'); + expect_char!(self, b'u', b'l', b'l'); Token::Null }, // whitespace @@ -326,13 +321,15 @@ impl<'a> Parser<'a> { fn value_from(&mut self, token: Token) -> JsonResult { Ok(match token { - Token::String(value) => JsonValue::String(value), - Token::Number(value) => JsonValue::Number(value), - Token::Boolean(value) => JsonValue::Boolean(value), - Token::Null => JsonValue::Null, - Token::BracketOn => return self.array(), - Token::BraceOn => return self.object(), - token => return Err(JsonError::unexpected_token(token)) + Token::String(value) => JsonValue::String(value), + Token::Number(value) => JsonValue::Number(value), + Token::Boolean(value) => JsonValue::Boolean(value), + Token::Null => JsonValue::Null, + Token::BracketOn => return self.array(), + Token::BraceOn => return self.object(), + token => { + return Err(JsonError::unexpected_token(token)) + } }) } diff --git a/src/value.rs b/src/value.rs index fc1e59e..4eaba76 100644 --- a/src/value.rs +++ b/src/value.rs @@ -62,8 +62,60 @@ impl JsonValue { } } - /// Deprecated because the return type is planned to change to - /// `Option` eventually down the road. + pub fn is_number(&self) -> bool { + match *self { + JsonValue::Number(_) => true, + _ => false, + } + } + + pub fn is_boolean(&self) -> bool { + match *self { + JsonValue::Boolean(_) => true, + _ => false + } + } + + pub fn is_null(&self) -> bool { + match *self { + JsonValue::Null => true, + _ => false, + } + } + + pub fn is_object(&self) -> bool { + match *self { + JsonValue::Object(_) => true, + _ => false, + } + } + + pub fn is_array(&self) -> bool { + match *self { + JsonValue::Array(_) => true, + _ => false, + } + } + + /// Checks whether the value is empty. Returns true for: + /// + /// - empty string (`""`) + /// - number `0` + /// - boolean `false` + /// - null + /// - empty array (`array![]`) + /// - empty object (`object!{}`) + pub fn is_empty(&self) -> bool { + match *self { + JsonValue::String(ref value) => value.is_empty(), + JsonValue::Number(ref value) => !value.is_normal(), + JsonValue::Boolean(ref value) => !value, + JsonValue::Null => true, + JsonValue::Array(ref value) => value.is_empty(), + JsonValue::Object(ref value) => value.is_empty(), + } + } + #[deprecated(since="0.6.1", note="Use `as_str` instead")] pub fn as_string(&self) -> JsonResult<&String> { match *self { @@ -79,13 +131,6 @@ impl JsonValue { } } - pub fn is_number(&self) -> bool { - match *self { - JsonValue::Number(_) => true, - _ => false, - } - } - #[deprecated(since="0.6.1", note="Use `as_f64` instead")] pub fn as_number(&self) -> JsonResult<&f64> { match *self { @@ -145,10 +190,10 @@ impl JsonValue { self.as_f64().and_then(|value| f64_to_singed!(isize, value)) } - pub fn is_boolean(&self) -> bool { + pub fn as_bool(&self) -> Option { match *self { - JsonValue::Boolean(_) => true, - _ => false + JsonValue::Boolean(ref value) => Some(*value), + _ => None } } @@ -160,34 +205,6 @@ impl JsonValue { } } - pub fn as_bool(&self) -> Option { - match *self { - JsonValue::Boolean(ref value) => Some(*value), - _ => None - } - } - - pub fn is_null(&self) -> bool { - match *self { - JsonValue::Null => true, - _ => false, - } - } - - pub fn is_object(&self) -> bool { - match *self { - JsonValue::Object(_) => true, - _ => false, - } - } - - pub fn is_array(&self) -> bool { - match *self { - JsonValue::Array(_) => true, - _ => false, - } - } - /// Works on `JsonValue::Object` - create or override key with value. #[must_use] #[deprecated(since="0.6.0", note="Use `object[key] = value.into()` instead")] @@ -262,6 +279,17 @@ impl JsonValue { } } + /// Works on `JsonValue::Array` - remove and return last element from + /// an array. On failure returns a null. + pub fn pop(&mut self) -> JsonValue { + match *self { + JsonValue::Array(ref mut vec) => { + vec.pop().unwrap_or(JsonValue::Null) + }, + _ => JsonValue::Null + } + } + /// Works on `JsonValue::Array` - gets a reference to a value at index. /// For most purposes consider using `array[index]` instead. #[deprecated(since="0.6.0", note="Use `array[index]` instead")] @@ -358,6 +386,29 @@ impl JsonValue { _ => EntriesMut::None } } + + /// Works on `JsonValue::Object` - remove a key and return the value it held. + /// If the key was not present, the method is called on anything but an + /// object, it will return a null. + pub fn remove(&mut self, key: &str) -> JsonValue { + match *self { + JsonValue::Object(ref mut btree) => { + btree.remove(key).unwrap_or(JsonValue::Null) + }, + _ => JsonValue::Null + } + } + + /// When called on an array or an object, will wipe them clean. When called + /// on a string will clear the string. Numbers and booleans become null. + pub fn clear(&mut self) { + match *self { + JsonValue::String(ref mut string) => string.clear(), + JsonValue::Object(ref mut btree) => btree.clear(), + JsonValue::Array(ref mut vec) => vec.clear(), + _ => *self = JsonValue::Null, + } + } } /// Implements indexing by `usize` to easily access array members: @@ -411,7 +462,7 @@ impl IndexMut for JsonValue { _ => { *self = JsonValue::new_array(); self.push(JsonValue::Null).unwrap(); - &mut self[0] + self.index_mut(index) } } } @@ -470,7 +521,7 @@ impl<'a> IndexMut<&'a str> for JsonValue { }, _ => { *self = JsonValue::new_object(); - &mut self[index] + self.index_mut(index) } } } diff --git a/tests/lib.rs b/tests/lib.rs index 6e0a133..86d1ac9 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -76,12 +76,28 @@ fn is_false() { } #[test] -fn is_nul() { +fn is_null() { let null = JsonValue::Null; assert!(null.is_null()); } +#[test] +fn is_empty() { + assert!(Null.is_empty()); + assert!(json::from(0).is_empty()); + assert!(json::from("").is_empty()); + assert!(json::from(false).is_empty()); + assert!(array![].is_empty()); + assert!(object!{}.is_empty()); + + assert!(!json::from(1).is_empty()); + assert!(!json::from("foo").is_empty()); + assert!(!json::from(true).is_empty()); + assert!(!array![0].is_empty()); + assert!(!object!{ "foo" => false }.is_empty()); +} + #[test] fn stringify_null() { assert_eq!(stringify(Null), "null"); @@ -119,6 +135,11 @@ fn stringify_integer() { assert_eq!(stringify(42), "42"); } +#[test] +fn stringify_small_number() { + assert_eq!(stringify(0.000000000000001), "0.000000000000001"); +} + #[test] fn stringify_true() { assert_eq!(stringify(true), "true"); @@ -238,34 +259,6 @@ fn stringify_pretty_object() { \"Brutus\",\n \"mother\": \"Helga\"\n }\n}"); } -#[test] -fn object_dump_minified() { - let object = object!{ - "name" => "Maciej", - "age" => 30 - }; - - assert_eq!(object.dump(), "{\"age\":30,\"name\":\"Maciej\"}"); -} - -#[test] -fn object_dump_pretty() { - let object = object!{ - "name" => "Urlich", - "age" => 50, - "parents" => object!{ - "mother" => "Helga", - "father" => "Brutus" - }, - "cars" => array![ "Golf", "Mercedes", "Porsche" ] - }; - - assert_eq!(object.pretty(2), - "{\n \"age\": 50,\n \"cars\": [\n \"Golf\",\n \"Mercedes\",\n \ - \"Porsche\"\n ],\n \"name\": \"Urlich\",\n \"parents\": {\n \"father\": \ - \"Brutus\",\n \"mother\": \"Helga\"\n }\n}"); -} - #[test] fn parse_true() { assert_eq!(parse("true").unwrap(), true); @@ -473,16 +466,6 @@ fn parse_error() { assert!(parse("10 20").is_err()); } -#[test] -fn object_len() { - let data = object!{ - "a" => true, - "b" => false - }; - - assert_eq!(data.len(), 2); -} - #[test] fn array_len() { let data = array![0, 1, 2, 3]; @@ -505,14 +488,72 @@ fn array_contains() { } #[test] -fn null_len() { - let data = json::Null; +fn array_push() { + let mut data = array![1, 2]; - assert_eq!(data.len(), 0); + data.push(3).unwrap(); + + assert_eq!(data, array![1, 2, 3]); +} + +#[test] +fn array_pop() { + let mut data = array![1, 2, 3]; + + assert_eq!(data.pop(), 3); + assert_eq!(data, array![1, 2]); +} + +#[test] +fn array_members() { + let data = array![1, "foo"]; + + for member in data.members() { + assert!(!member.is_null()); + } + + let mut members = data.members(); + + assert_eq!(members.next().unwrap(), 1); + assert_eq!(members.next().unwrap(), "foo"); + assert!(members.next().is_none()); +} + +#[test] +fn array_members_mut() { + let mut data = array![Null, Null]; + + for member in data.members_mut() { + assert!(member.is_null()); + *member = 100.into(); + } + + assert_eq!(data, array![100, 100]); +} + +#[test] +fn object_len() { + let data = object!{ + "a" => true, + "b" => false + }; + + assert_eq!(data.len(), 2); } #[test] -fn iter_entries() { +fn object_remove() { + let mut data = object!{ + "foo" => "bar", + "answer" => 42 + }; + + assert_eq!(data.remove("foo"), "bar"); + assert_eq!(data, object!{ "answer" => 42 }); +} + +#[test] +fn object_entries() { let data = object!{ "a" => 1, "b" => "foo" @@ -536,7 +577,7 @@ fn iter_entries() { } #[test] -fn iter_entries_mut() { +fn object_entries_mut() { let mut data = object!{ "a" => Null, "b" => Null @@ -554,30 +595,38 @@ fn iter_entries_mut() { } #[test] -fn iter_members() { - let data = array![1, "foo"]; +fn object_dump_minified() { + let object = object!{ + "name" => "Maciej", + "age" => 30 + }; - for member in data.members() { - assert!(!member.is_null()); - } + assert_eq!(object.dump(), "{\"age\":30,\"name\":\"Maciej\"}"); +} - let mut members = data.members(); +#[test] +fn object_dump_pretty() { + let object = object!{ + "name" => "Urlich", + "age" => 50, + "parents" => object!{ + "mother" => "Helga", + "father" => "Brutus" + }, + "cars" => array![ "Golf", "Mercedes", "Porsche" ] + }; - assert_eq!(members.next().unwrap(), 1); - assert_eq!(members.next().unwrap(), "foo"); - assert!(members.next().is_none()); + assert_eq!(object.pretty(2), + "{\n \"age\": 50,\n \"cars\": [\n \"Golf\",\n \"Mercedes\",\n \ + \"Porsche\"\n ],\n \"name\": \"Urlich\",\n \"parents\": {\n \"father\": \ + \"Brutus\",\n \"mother\": \"Helga\"\n }\n}"); } #[test] -fn iter_members_mut() { - let mut data = array![Null, Null]; - - for member in data.members_mut() { - assert!(member.is_null()); - *member = 100.into(); - } +fn null_len() { + let data = json::Null; - assert_eq!(data, array![100, 100]); + assert_eq!(data.len(), 0); } #[test]