Implement issue #68 - add pointer_mut

Move `pointer` function for `Value` into `Pointer` trait.
Add `pointer_mut` and `pointer_owned` to `Pointer` trait.

Made oli-obk suggested changes
This commit is contained in:
CryptArchy 2016-12-03 15:47:21 -06:00
parent 8d209a67bf
commit a32de277b5
3 changed files with 158 additions and 12 deletions

View File

@ -97,6 +97,13 @@ pub enum Value {
Object(Map<String, Value>),
}
fn parse_index(s: &str) -> Option<usize> {
if s.starts_with('+') || (s.starts_with('0') && s.len() != 1) {
return None;
}
s.parse().ok()
}
impl Value {
/// If the `Value` is an Object, returns the value associated with the provided key.
/// Otherwise, returns None.
@ -161,26 +168,85 @@ impl Value {
///
/// 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 tokens = pointer.split('/').skip(1).map(|x| x.replace("~1", "/").replace("~0", "~"));
let mut target = self;
for escaped_token in pointer.split('/').skip(1) {
let token = escaped_token.replace("~1", "/").replace("~0", "~");
for token in tokens {
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))
}
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)
}
/// Looks up a value by a JSON Pointer and returns a mutable reference to
/// that value.
///
/// 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).
///
/// # Example of Use
///
/// ```rust
/// extern crate serde_json;
///
/// use serde_json::Value;
/// use std::mem;
///
/// fn main() {
/// let s = r#"{"x": 1.0, "y": 2.0}"#;
/// let mut value: Value = serde_json::from_str(s).unwrap();
///
/// // Check value using read-only pointer
/// assert_eq!(value.pointer("/x"), Some(&Value::F64(1.0)));
/// // Change value with direct assignment
/// *value.pointer_mut("/x").unwrap() = Value::F64(1.5);
/// // Check that new value was written
/// assert_eq!(value.pointer("/x"), Some(&Value::F64(1.5)));
///
/// // "Steal" ownership of a value. Can replace with any valid Value.
/// let old_x = value.pointer_mut("/x").map(|x| mem::replace(x, Value::Null)).unwrap();
/// assert_eq!(old_x, Value::F64(1.5));
/// assert_eq!(value.pointer("/x").unwrap(), &Value::Null);
/// }
/// ```
pub fn pointer_mut<'a>(&'a mut self, pointer: &str) -> Option<&'a mut Value> {
if pointer == "" {
return Some(self);
}
if !pointer.starts_with('/') {
return None;
}
let tokens = pointer.split('/').skip(1).map(|x| x.replace("~1", "/").replace("~0", "~"));
let mut target = self;
for token in tokens {
// borrow checker gets confused about `target` being mutably borrowed too many times because of the loop
// this once-per-loop binding makes the scope clearer and circumvents the error
let target_once = target;
let target_opt = match *target_once {
Value::Object(ref mut map) => map.get_mut(&token),
Value::Array(ref mut list) => parse_index(&token).and_then(move |x| list.get_mut(x)),
_ => return None,
};
if let Some(t) = target_opt {

View File

@ -98,3 +98,27 @@ fn bench_deserializer_unicode(b: &mut Bencher) {
let _s: String = serde_json::from_str(&s).unwrap();
});
}
#[bench]
fn bench_pointer(b: &mut Bencher) {
use serde_json::{self, Value};
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();
b.iter(|| {
let _ = data.pointer("").unwrap();
let _ = data.pointer("/foo/0").unwrap();
let _ = data.pointer("/unknown");
});
}

View File

@ -1657,6 +1657,62 @@ fn test_json_pointer() {
assert!(data.pointer("/foo/01").is_none());
}
#[test]
fn test_json_pointer_mut() {
use std::mem;
// Test case taken from https://tools.ietf.org/html/rfc6901#page-5
let mut 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();
// Basic pointer checks
assert_eq!(data.pointer_mut("/foo").unwrap(),
&Value::Array(vec![Value::String("bar".to_owned()),
Value::String("baz".to_owned())]));
assert_eq!(data.pointer_mut("/foo/0").unwrap(),
&Value::String("bar".to_owned()));
assert_eq!(data.pointer_mut("/").unwrap(), &Value::U64(0));
assert_eq!(data.pointer_mut("/a~1b").unwrap(), &Value::U64(1));
assert_eq!(data.pointer_mut("/c%d").unwrap(), &Value::U64(2));
assert_eq!(data.pointer_mut("/e^f").unwrap(), &Value::U64(3));
assert_eq!(data.pointer_mut("/g|h").unwrap(), &Value::U64(4));
assert_eq!(data.pointer_mut("/i\\j").unwrap(), &Value::U64(5));
assert_eq!(data.pointer_mut("/k\"l").unwrap(), &Value::U64(6));
assert_eq!(data.pointer_mut("/ ").unwrap(), &Value::U64(7));
assert_eq!(data.pointer_mut("/m~0n").unwrap(), &Value::U64(8));
// Invalid pointers
assert!(data.pointer_mut("/unknown").is_none());
assert!(data.pointer_mut("/e^f/ertz").is_none());
assert!(data.pointer_mut("/foo/00").is_none());
assert!(data.pointer_mut("/foo/01").is_none());
// Mutable pointer checks
*data.pointer_mut("/").unwrap() = Value::U64(100);
assert_eq!(data.pointer("/").unwrap(), &Value::U64(100));
*data.pointer_mut("/foo/0").unwrap() = Value::String("buzz".to_owned());
assert_eq!(data.pointer("/foo/0").unwrap(), &Value::String("buzz".to_owned()));
// Example of ownership stealing
assert_eq!(data.pointer_mut("/a~1b").map(|m| mem::replace(m, Value::Null)).unwrap(), Value::U64(1));
assert_eq!(data.pointer("/a~1b").unwrap(), &Value::Null);
// Need to compare against a clone so we don't anger the borrow checker
// by taking out two references to a mutable value
let mut d2 = data.clone();
assert_eq!(data.pointer_mut("").unwrap(), &mut d2);
}
#[test]
fn test_stack_overflow() {
let brackets: String = iter::repeat('[').take(127).chain(iter::repeat(']').take(127)).collect();