Skip to content

Add as_borrow() generic method for Value #153

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions json/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ use std::result;
use serde::de;
use serde::ser;

use value::ValueType;

/// The errors that can arise while parsing a JSON stream.
#[derive(Clone, PartialEq, Debug)]
pub enum ErrorCode {
Expand Down Expand Up @@ -256,3 +258,25 @@ impl ser::Error for Error {

/// Helper alias for `Result` objects that return a JSON `Error`.
pub type Result<T> = result::Result<T, Error>;

/// Represents type mismatch
#[derive(Debug)]
pub struct TypeError {
/// Type which was required
pub expected: ValueType,

/// Type which was found in Json
pub provided: ValueType,
}

impl error::Error for TypeError {
fn description(&self) -> &str {
"type mismatch"
}
}

impl fmt::Display for TypeError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "expected type: {} found type: {}", self.expected, self.provided)
}
}
317 changes: 316 additions & 1 deletion json/src/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ use num_traits::NumCast;
use serde::de;
use serde::ser;

use error::{Error, ErrorCode};
use error::{Error, ErrorCode, TypeError};

/// Represents a key/value type.
#[cfg(not(feature = "preserve_order"))]
Expand Down Expand Up @@ -215,6 +215,92 @@ impl Value {
}
}

/// If the `Value` is of type `T`, returns borrow of internal value.
/// Returns Err otherwise.
///
/// Useful for generics with larger/allocated types to speed things up.
///
/// # Example
///
/// ```rust
/// extern crate serde_json;
///
/// use serde_json::{to_value,Value};
/// use serde_json::value::ValueType;
/// use std::collections::BTreeMap;
///
/// fn main() {
/// let null = to_value(());
/// let boolean = to_value(true);
/// let int64 = to_value(-42i64);
/// let uint64 = to_value(18446744073709551337u64);
/// let float = to_value(3.1415926535f64);
/// let string = to_value("Hello world!");
/// let array = to_value(&[1, 2, 3, 4, 5]);
/// let mut map = BTreeMap::new();
/// map.insert("foo", to_value("bar"));
/// let map = to_value(&map);
///
/// let _: &() = null.as_borrow().unwrap();
/// let _: &bool = boolean.as_borrow().unwrap();
/// let _: &i64 = int64.as_borrow().unwrap();
/// let _: &u64 = uint64.as_borrow().unwrap();
/// let _: &f64 = float.as_borrow().unwrap();
/// let _: &str = string.as_borrow().unwrap();
/// let _: &String = string.as_borrow().unwrap();
/// let _: &[Value] = array.as_borrow().unwrap();
/// let _: &Vec<Value> = array.as_borrow().unwrap();
/// let _: &BTreeMap<String, Value> = map.as_borrow().unwrap();
/// }
pub fn as_borrow<'a, T: TryBorrow<'a>>(&'a self) -> Result<T, TypeError> {
T::try_borrow(self)
}

/// If the `Value` is of type `T`, returns mutable borrow of internal value.
/// Returns Err otherwise.
///
/// Useful for generics with larger/allocated types to speed things up.
///
/// # Example
///
/// ```rust
/// extern crate serde_json;
///
/// use serde_json::{to_value,Value};
/// use serde_json::value::ValueType;
/// use std::collections::BTreeMap;
///
/// fn main() {
/// let mut null = to_value(());
/// let mut boolean = to_value(true);
/// let mut int64 = to_value(-42i64);
/// let mut uint64 = to_value(18446744073709551337u64);
/// let mut float = to_value(3.1415926535f64);
/// let mut string = to_value("Hello world!");
/// let mut array = to_value(&[1, 2, 3, 4, 5]);
/// let mut map = BTreeMap::new();
/// map.insert("foo", to_value("bar"));
/// let mut map = to_value(&map);
///
/// let _: &mut () = null.as_borrow_mut().unwrap();
/// let _: &mut bool = boolean.as_borrow_mut().unwrap();
/// let _: &mut i64 = int64.as_borrow_mut().unwrap();
/// let _: &mut u64 = uint64.as_borrow_mut().unwrap();
/// let _: &mut f64 = float.as_borrow_mut().unwrap();
/// {
/// let _: &mut str = string.as_borrow_mut().unwrap();
/// }
/// let _: &mut String = string.as_borrow_mut().unwrap();
/// {
/// let _: &mut [Value] = array.as_borrow_mut().unwrap();
/// }
/// let _: &mut Vec<Value> = array.as_borrow_mut().unwrap();
/// let _: &mut BTreeMap<String, Value> = map.as_borrow_mut().unwrap();
/// }
pub fn as_borrow_mut<'a, T: TryBorrowMut<'a>>(&'a mut self) -> Result<T, TypeError> {
T::try_borrow_mut(self)
}

/// Returns true if the `Value` is an Object. Returns false otherwise.
pub fn is_object(&self) -> bool {
self.as_object().is_some()
Expand Down Expand Up @@ -473,6 +559,99 @@ impl de::Deserialize for Value {
}
}

/// Represents type of a JSON value
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
pub enum ValueType {
Copy link
Member

@oli-obk oli-obk Sep 19, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This type sounds a lot like std::mem::discriminant_value, so we could just wait until that is stable (3 Months probably)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. The library is still unstable, so we can wait. I hope time is not concern to anyone else.

I think I'm going to leave this as is for now until discriminant is stabilized. However, I'm not sure whether discriminant will allow to do exactly what I mean. I will check that out.

/// Represents a JSON null value type
Null,

/// Represents a JSON Boolean type
Bool,

/// Represents a JSON signed integer type
I64,

/// Represents a JSON unsigned integer type
U64,

/// Represents a JSON floating point number type
F64,

/// Represents a JSON string type
String,

/// Represents a JSON array type
Array,

/// Represents a JSON object type
Object,
}

impl ValueType {

/// Returns type of a value
///
/// # Example
///
/// ```rust
/// extern crate serde_json;
///
/// use serde_json::to_value;
/// use serde_json::value::ValueType;
/// use std::collections::BTreeMap;
///
/// fn main() {
/// let null = to_value(());
/// let boolean = to_value(true);
/// let int64 = to_value(-42i64);
/// let uint64 = to_value(18446744073709551337u64);
/// let float = to_value(3.1415926535f64);
/// let string = to_value("Hello world!");
/// let array = to_value(&[1, 2, 3, 4, 5]);
/// let mut map = BTreeMap::new();
/// map.insert("foo", to_value("bar"));
/// let map = to_value(&map);
///
/// assert_eq!(ValueType::of(&null), ValueType::Null);
/// assert_eq!(ValueType::of(&boolean), ValueType::Bool);
/// assert_eq!(ValueType::of(&int64), ValueType::I64);
/// assert_eq!(ValueType::of(&uint64), ValueType::U64);
/// assert_eq!(ValueType::of(&float), ValueType::F64);
/// assert_eq!(ValueType::of(&string), ValueType::String);
/// assert_eq!(ValueType::of(&array), ValueType::Array);
/// assert_eq!(ValueType::of(&map), ValueType::Object);
/// }
pub fn of(val: &Value) -> Self {
match *val {
Value::Null => ValueType::Null,
Value::Bool(_) => ValueType::Bool,
Value::I64(_) => ValueType::I64,
Value::U64(_) => ValueType::U64,
Value::F64(_) => ValueType::F64,
Value::String(_) => ValueType::String,
Value::Array(_) => ValueType::Array,
Value::Object(_) => ValueType::Object,
}
}
}

impl fmt::Display for ValueType {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
use self::ValueType::*;

write!(f, "{}", match *self {
Null => "Null",
Bool => "Bool",
I64 => "I64",
U64 => "U64",
F64 => "F64",
String => "String",
Array => "Array",
Object => "Object",
})
}
}

struct WriterFormatter<'a, 'b: 'a> {
inner: &'a mut fmt::Formatter<'b>,
}
Expand Down Expand Up @@ -1333,3 +1512,139 @@ impl<T: ?Sized> ToJson for T
to_value(&self)
}
}

/// Just like `std::borrow::Borrow<Value>` but borrowing may fail.
pub trait TryBorrow<'a>: 'a + ::std::marker::Sized {
/// Returns reference to internal data if type matches.
fn try_borrow(value: &'a Value) -> Result<Self, TypeError>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this trait could include the try_borrow_mut method

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason is to mimic Borrow and BorrowMut. I'm not even sure whether it would be possible to have them together in one trait, since one is defined for &'a T and the other for &'a mut T...

However, I'm open to put them together, if it's possible and advantageous.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I forgot about that

}

impl<'a> TryBorrow<'a> for &'a () {
fn try_borrow(value: &'a Value) -> Result<Self, TypeError> {
static EMPTY: () = ();
if value.is_null() { Ok(&EMPTY) } else { Err(TypeError { expected: ValueType::Null, provided: ValueType::of(value) }) }
}
}

impl<'a> TryBorrow<'a> for &'a bool {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all these impls should be abstracted away into a macro

Copy link
Author

@Kixunil Kixunil Sep 20, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking about it too lately. I'm not sure how to define the macro to be readable enough. I was thinking about something like this:

impl_try_borrows!( Bool => bool, I64 => i64, /* etc. */ );

What do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good to me

fn try_borrow(value: &'a Value) -> Result<Self, TypeError> {
if let &Value::Bool(ref val) = value { Ok(val) } else { Err(TypeError { expected: ValueType::Bool, provided: ValueType::of(value) }) }
}
}

impl<'a> TryBorrow<'a> for &'a u64 {
fn try_borrow(value: &'a Value) -> Result<Self, TypeError> {
if let &Value::U64(ref val) = value { Ok(val) } else { Err(TypeError { expected: ValueType::U64, provided: ValueType::of(value) }) }
}
}

impl<'a> TryBorrow<'a> for &'a i64 {
fn try_borrow(value: &'a Value) -> Result<Self, TypeError> {
if let &Value::I64(ref val) = value { Ok(val) } else { Err(TypeError { expected: ValueType::I64, provided: ValueType::of(value) }) }
}
}

impl<'a> TryBorrow<'a> for &'a f64 {
fn try_borrow(value: &'a Value) -> Result<Self, TypeError> {
if let &Value::F64(ref val) = value { Ok(val) } else { Err(TypeError { expected: ValueType::F64, provided: ValueType::of(value) }) }
}
}

impl<'a> TryBorrow<'a> for &'a str {
fn try_borrow(value: &'a Value) -> Result<Self, TypeError> {
if let &Value::String(ref val) = value { Ok(val) } else { Err(TypeError { expected: ValueType::String, provided: ValueType::of(value) }) }
}
}

impl<'a> TryBorrow<'a> for &'a String {
fn try_borrow(value: &'a Value) -> Result<Self, TypeError> {
if let &Value::String(ref val) = value { Ok(val) } else { Err(TypeError { expected: ValueType::String, provided: ValueType::of(value) }) }
}
}

impl<'a> TryBorrow<'a> for &'a [Value] {
fn try_borrow(value: &'a Value) -> Result<Self, TypeError> {
if let &Value::Array(ref val) = value { Ok(val) } else { Err(TypeError { expected: ValueType::Array, provided: ValueType::of(value) }) }
}
}

impl<'a> TryBorrow<'a> for &'a Vec<Value> {
fn try_borrow(value: &'a Value) -> Result<Self, TypeError> {
if let &Value::Array(ref val) = value { Ok(val) } else { Err(TypeError { expected: ValueType::Array, provided: ValueType::of(value) }) }
}
}

impl<'a> TryBorrow<'a> for &'a Map<String, Value> {
fn try_borrow(value: &'a Value) -> Result<Self, TypeError> {
if let &Value::Object(ref val) = value { Ok(val) } else { Err(TypeError { expected: ValueType::Object, provided: ValueType::of(value) }) }
}
}

/// Just like `std::borrow::BorrowMut` but borrowing may fail.
pub trait TryBorrowMut<'a>: 'a + ::std::marker::Sized {
/// Returns mutable reference to internal data if type matches.
fn try_borrow_mut(value: &'a mut Value) -> Result<Self, TypeError>;
}

impl<'a> TryBorrowMut<'a> for &'a mut () {
fn try_borrow_mut(value: &'a mut Value) -> Result<Self, TypeError> {
static mut EMPTY: () = ();
// Use of static &mut () is always safe, because it's value actually can't be changed
if value.is_null() { unsafe { Ok(&mut EMPTY) } } else { Err(TypeError { expected: ValueType::Null, provided: ValueType::of(value) }) }
}
}

impl<'a> TryBorrowMut<'a> for &'a mut bool {
fn try_borrow_mut(value: &'a mut Value) -> Result<Self, TypeError> {
if let &mut Value::Bool(ref mut val) = value { Ok(val) } else { Err(TypeError { expected: ValueType::Bool, provided: ValueType::of(value) }) }
}
}

impl<'a> TryBorrowMut<'a> for &'a mut u64 {
fn try_borrow_mut(value: &'a mut Value) -> Result<Self, TypeError> {
if let &mut Value::U64(ref mut val) = value { Ok(val) } else { Err(TypeError { expected: ValueType::U64, provided: ValueType::of(value) }) }
}
}

impl<'a> TryBorrowMut<'a> for &'a mut i64 {
fn try_borrow_mut(value: &'a mut Value) -> Result<Self, TypeError> {
if let &mut Value::I64(ref mut val) = value { Ok(val) } else { Err(TypeError { expected: ValueType::I64, provided: ValueType::of(value) }) }
}
}

impl<'a> TryBorrowMut<'a> for &'a mut f64 {
fn try_borrow_mut(value: &'a mut Value) -> Result<Self, TypeError> {
if let &mut Value::F64(ref mut val) = value { Ok(val) } else { Err(TypeError { expected: ValueType::F64, provided: ValueType::of(value) }) }
}
}

impl<'a> TryBorrowMut<'a> for &'a mut str {
fn try_borrow_mut(value: &'a mut Value) -> Result<Self, TypeError> {
if let &mut Value::String(ref mut val) = value { Ok(val) } else { Err(TypeError { expected: ValueType::String, provided: ValueType::of(value) }) }
}
}

impl<'a> TryBorrowMut<'a> for &'a mut String {
fn try_borrow_mut(value: &'a mut Value) -> Result<Self, TypeError> {
if let &mut Value::String(ref mut val) = value { Ok(val) } else { Err(TypeError { expected: ValueType::String, provided: ValueType::of(value) }) }
}
}

impl<'a> TryBorrowMut<'a> for &'a mut [Value] {
fn try_borrow_mut(value: &'a mut Value) -> Result<Self, TypeError> {
if let &mut Value::Array(ref mut val) = value { Ok(val) } else { Err(TypeError { expected: ValueType::Array, provided: ValueType::of(value) }) }
}
}

impl<'a> TryBorrowMut<'a> for &'a mut Vec<Value> {
fn try_borrow_mut(value: &'a mut Value) -> Result<Self, TypeError> {
if let &mut Value::Array(ref mut val) = value { Ok(val) } else { Err(TypeError { expected: ValueType::Array, provided: ValueType::of(value) }) }
}
}

impl<'a> TryBorrowMut<'a> for &'a mut Map<String, Value> {
fn try_borrow_mut(value: &'a mut Value) -> Result<Self, TypeError> {
if let &mut Value::Object(ref mut val) = value { Ok(val) } else { Err(TypeError { expected: ValueType::Object, provided: ValueType::of(value) }) }
}
}