Auto merge of #41 - pyfisch:json-pointer, r=erickt

feat(value): implement RFC6901 JSON Pointer

Add a method `pointer(&str)` to `Value`. Deprecate `lookup(&str)`.
Bump version to 0.7.1.

Closes #9

Notes:
* It should be possible to add a `pointer_mut(&str)` method to Value. This would allow to add and modify values. (Maybe even a delete method) I failed to add such a method because of borrow checker.
* Should [RFC6902 JSON Patch](https://tools.ietf.org/html/rfc6902) be implemented or is this something for a separate crate? (Patch is based on Pointer)
This commit is contained in:
Homu 2016-05-13 17:41:41 +09:00
commit aa427ae1cc
3 changed files with 80 additions and 1 deletions

View File

@ -1,6 +1,6 @@
[package]
name = "serde_json"
version = "0.7.0"
version = "0.7.1"
authors = ["Erick Tryzelaar <erick.tryzelaar@gmail.com>"]
license = "MIT/Apache-2.0"
description = "A JSON serialization file format"

View File

@ -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<usize> {
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.

View File

@ -1441,3 +1441,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());
}