mirror of
https://github.com/serde-rs/json.git
synced 2025-10-02 07:21:29 +00:00
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:
parent
8d209a67bf
commit
a32de277b5
@ -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 {
|
||||
|
@ -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");
|
||||
});
|
||||
}
|
||||
|
@ -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();
|
||||
|
Loading…
x
Reference in New Issue
Block a user