From 6fa1af01a8af9d9d41396f345f68652f48a6355e Mon Sep 17 00:00:00 2001 From: Pyfisch Date: Sat, 27 Feb 2016 16:06:59 +0100 Subject: [PATCH] feat(value): implement RFC6901 JSON Pointer Add a method `pointer(&str)` to `Value`. Deprecate `lookup(&str)`. Bump version to 0.7.1. Closes #9 --- json/Cargo.toml | 2 +- json/src/value.rs | 42 +++++++++++++++++++++++++++++++++++ json_tests/tests/test_json.rs | 37 ++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 1 deletion(-) diff --git a/json/Cargo.toml b/json/Cargo.toml index 3c4765c..0a82dc7 100644 --- a/json/Cargo.toml +++ b/json/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "serde_json" -version = "0.7.0" +version = "0.7.1" authors = ["Erick Tryzelaar "] license = "MIT/Apache-2.0" description = "A JSON serialization file format" diff --git a/json/src/value.rs b/json/src/value.rs index efffb92..bdac819 100644 --- a/json/src/value.rs +++ b/json/src/value.rs @@ -98,6 +98,8 @@ impl Value { Some(target) } + /// **Deprecated**: Use `Value.pointer()` and pointer syntax instead. + /// /// Looks up a value by path. /// /// This is a convenience method that splits the path by `'.'` @@ -120,6 +122,46 @@ impl Value { Some(target) } + /// Looks up a value by a JSON Pointer. + /// + /// JSON Pointer defines a string syntax for identifying a specific value + /// within a JavaScript Object Notation (JSON) document. + /// + /// A Pointer is a Unicode string with the reference tokens separated by `/`. + /// Inside tokens `/` is replaced by `~1` and `~` is replaced by `~0`. The + /// addressed value is returned and if there is no such value `None` is + /// returned. + /// + /// For more information read [RFC6901](https://tools.ietf.org/html/rfc6901). + pub fn pointer<'a>(&'a self, pointer: &str) -> Option<&'a Value> { + fn parse_index(s: &str) -> Option { + if s.starts_with("+") || (s.starts_with("0") && s.len() != 1) { + return None + } + s.parse().ok() + } + if pointer == "" { + return Some(self); + } + if !pointer.starts_with('/') { + return None; + } + let mut target = self; + for escaped_token in pointer.split('/').skip(1) { + let token = escaped_token.replace("~1", "/").replace("~0", "~"); + let target_opt = match target { + &Value::Object(ref map) => map.get(&token[..]), + &Value::Array(ref list) => parse_index(&token[..]) + .and_then(|x| list.get(x)), + _ => return None, + }; + if let Some(t) = target_opt { + target = t; + } else { return None } + } + Some(target) + } + /// If the `Value` is an Object, performs a depth-first search until /// a value associated with the provided key is found. If no value is found /// or the `Value` is not an Object, returns None. diff --git a/json_tests/tests/test_json.rs b/json_tests/tests/test_json.rs index fa765fd..716b719 100644 --- a/json_tests/tests/test_json.rs +++ b/json_tests/tests/test_json.rs @@ -1447,3 +1447,40 @@ fn test_json_stream_empty() { assert!(parsed.next().is_none()); } + +#[test] +fn test_json_pointer() { + // Test case taken from https://tools.ietf.org/html/rfc6901#page-5 + let data: Value = serde_json::from_str(r#"{ + "foo": ["bar", "baz"], + "": 0, + "a/b": 1, + "c%d": 2, + "e^f": 3, + "g|h": 4, + "i\\j": 5, + "k\"l": 6, + " ": 7, + "m~n": 8 + }"#).unwrap(); + assert_eq!(data.pointer("").unwrap(), &data); + assert_eq!(data.pointer("/foo").unwrap(), + &Value::Array(vec![Value::String("bar".to_owned()), + Value::String("baz".to_owned())])); + assert_eq!(data.pointer("/foo/0").unwrap(), + &Value::String("bar".to_owned())); + assert_eq!(data.pointer("/").unwrap(), &Value::U64(0)); + assert_eq!(data.pointer("/a~1b").unwrap(), &Value::U64(1)); + assert_eq!(data.pointer("/c%d").unwrap(), &Value::U64(2)); + assert_eq!(data.pointer("/e^f").unwrap(), &Value::U64(3)); + assert_eq!(data.pointer("/g|h").unwrap(), &Value::U64(4)); + assert_eq!(data.pointer("/i\\j").unwrap(), &Value::U64(5)); + assert_eq!(data.pointer("/k\"l").unwrap(), &Value::U64(6)); + assert_eq!(data.pointer("/ ").unwrap(), &Value::U64(7)); + assert_eq!(data.pointer("/m~0n").unwrap(), &Value::U64(8)); + // Invalid pointers + assert!(data.pointer("/unknown").is_none()); + assert!(data.pointer("/e^f/ertz").is_none()); + assert!(data.pointer("/foo/00").is_none()); + assert!(data.pointer("/foo/01").is_none()); +}