Fun with traits for better ergonomics of the JSONValue Enum
This commit is contained in:
parent
a4cb3f9b0e
commit
97bfd16b32
237
src/lib.rs
237
src/lib.rs
@ -7,6 +7,7 @@ use std::iter::FromIterator;
|
|||||||
use std::{char, u16};
|
use std::{char, u16};
|
||||||
|
|
||||||
pub type JSONResult = Result<JSONValue, ParseError>;
|
pub type JSONResult = Result<JSONValue, ParseError>;
|
||||||
|
pub type JSONArray = Vec<JSONValue>;
|
||||||
pub type JSONMap = HashMap<String, JSONValue>;
|
pub type JSONMap = HashMap<String, JSONValue>;
|
||||||
|
|
||||||
/// The type of JSON value
|
/// The type of JSON value
|
||||||
@ -34,6 +35,139 @@ pub enum JSONValue {
|
|||||||
Null,
|
Null,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl JSONValue {
|
||||||
|
/// Convert the wrapped JSONValue to its simpler rust value
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```
|
||||||
|
/// use naive_json_parser::JSONValue;
|
||||||
|
///
|
||||||
|
/// let str = "Four score and seven years ago...";
|
||||||
|
/// let wrapped = JSONValue::from(str);
|
||||||
|
///
|
||||||
|
/// // s is now the `String` that was in the `JSONValue` enum
|
||||||
|
/// let s: String = wrapped.unwrap();
|
||||||
|
///
|
||||||
|
/// # assert_eq!(str, &s);
|
||||||
|
/// ```
|
||||||
|
pub fn unwrap<T: From<JSONValue>>(self) -> T {
|
||||||
|
T::from(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<JSONValue> for JSONMap {
|
||||||
|
/// Extracts the `HashMap` in the `JSONValue` enum, if it exists.
|
||||||
|
/// Otherwise, panics.
|
||||||
|
fn from(val: JSONValue) -> JSONMap {
|
||||||
|
match val {
|
||||||
|
JSONValue::Object(o) => o,
|
||||||
|
_ => panic!("Invalid type conversion")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<JSONValue> for JSONArray{
|
||||||
|
/// Extracts the `Vec` in the `JSONValue` enum, if it exists.
|
||||||
|
/// Otherwise, panics.
|
||||||
|
fn from(val: JSONValue) -> JSONArray {
|
||||||
|
match val {
|
||||||
|
JSONValue::Array(a) => a,
|
||||||
|
_ => panic!("Invalid type conversion")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<JSONValue> for f64 {
|
||||||
|
/// Extracts the `f64` in the `JSONValue` enum, if it exists.
|
||||||
|
/// Otherwise, panics.
|
||||||
|
fn from(val: JSONValue) -> f64 {
|
||||||
|
match val {
|
||||||
|
JSONValue::Number(n) => n,
|
||||||
|
_ => panic!("Invalid type conversion")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<JSONValue> for String {
|
||||||
|
/// Extracts the `String` in the `JSONValue` enum, if it exists.
|
||||||
|
/// Otherwise, panics.
|
||||||
|
fn from(val: JSONValue) -> String {
|
||||||
|
match val {
|
||||||
|
JSONValue::String(s) => s,
|
||||||
|
_ => panic!("Invalid type conversion")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<JSONValue> for bool {
|
||||||
|
/// Extracts the `bool` value from the `JSONValue` enum, if it exists.
|
||||||
|
/// Otherwise, panics.
|
||||||
|
fn from(val: JSONValue) -> bool {
|
||||||
|
match val {
|
||||||
|
JSONValue::True => true,
|
||||||
|
JSONValue::False => false,
|
||||||
|
_ => panic!("Invalid type conversion")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<JSONValue> for () {
|
||||||
|
/// This will just swallow the enum value and return a unit tuple
|
||||||
|
fn from(_: JSONValue) -> () { () }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<JSONMap> for JSONValue {
|
||||||
|
/// Wraps the `HashMap` in the `JSONValue` enum
|
||||||
|
fn from(val: JSONMap) -> JSONValue {
|
||||||
|
Self::Object(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<JSONArray> for JSONValue {
|
||||||
|
/// Wraps the `Vec` in the `JSONValue` enum
|
||||||
|
fn from(val: JSONArray) -> JSONValue {
|
||||||
|
Self::Array(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<bool> for JSONValue {
|
||||||
|
/// Sets the `JSONValue` enum to the `True` or `False` value
|
||||||
|
fn from(val: bool) -> Self {
|
||||||
|
match val {
|
||||||
|
true => Self::True,
|
||||||
|
false => Self::False,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<f64> for JSONValue {
|
||||||
|
/// Wraps the `f64` in the `JSONValue` enum
|
||||||
|
fn from(n: f64) -> Self {
|
||||||
|
Self::Number(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<()> for JSONValue {
|
||||||
|
/// Sets the `JSONValue` enum to the `Null` value
|
||||||
|
fn from(_s: ()) -> Self {
|
||||||
|
Self::Null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for JSONValue {
|
||||||
|
/// Wraps the `String` in the `JSONValue` enum
|
||||||
|
fn from(s: String) -> Self {
|
||||||
|
Self::String(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for JSONValue {
|
||||||
|
/// Creates a `String` and wraps it in the `JSONValue` enum
|
||||||
|
fn from(s: &str) -> Self {
|
||||||
|
Self::String(String::from(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The type of error returned by the parser
|
/// The type of error returned by the parser
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum ParseError {
|
pub enum ParseError {
|
||||||
@ -111,9 +245,7 @@ impl JSON {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Every parser failed, so the syntax is probably incorrect
|
// Every parser failed, so the syntax is probably incorrect
|
||||||
Err(ParseError::UnexpectedEndOfInput(String::from(
|
Err(ParseError::UnexpectedEndOfInput(format!("Doesn't seem to be valid JSON")))
|
||||||
"Doesn't seem to be valid JSON",
|
|
||||||
)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// See if there's a `JSONValue::Object` next in the JSON
|
/// See if there's a `JSONValue::Object` next in the JSON
|
||||||
@ -315,7 +447,7 @@ impl JSON {
|
|||||||
match str.parse::<f64>() {
|
match str.parse::<f64>() {
|
||||||
Ok(number) => {
|
Ok(number) => {
|
||||||
self.increment(str.len());
|
self.increment(str.len());
|
||||||
return Ok(Some(JSONValue::Number(number)));
|
return Ok(Some(JSONValue::from(number)));
|
||||||
}
|
}
|
||||||
Err(e) => Err(ParseError::ExpectedDigit(format!("'{}', {:#?}", str, e))),
|
Err(e) => Err(ParseError::ExpectedDigit(format!("'{}', {:#?}", str, e))),
|
||||||
}
|
}
|
||||||
@ -391,20 +523,39 @@ mod tests {
|
|||||||
use super::JSONValue::{Array, False, Null, Number, Object, True};
|
use super::JSONValue::{Array, False, Null, Number, Object, True};
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
impl JSONValue {
|
#[test]
|
||||||
fn unwrap_object(self) -> JSONMap {
|
fn value_conversion() {
|
||||||
match self {
|
let map: JSONMap = HashMap::new();
|
||||||
JSONValue::Object(o) => o,
|
let num = 9.380831539;
|
||||||
_ => panic!("Tried to unwrap a non-object"),
|
let str = "applesauce";
|
||||||
}
|
let arr: JSONArray = vec![JSONValue::from(map.clone()), JSONValue::from(num), JSONValue::from(str)];
|
||||||
|
|
||||||
|
assert_eq!(map.clone(), JSONMap::from(JSONValue::from(map.clone())));
|
||||||
|
assert_eq!(num, f64::from(JSONValue::from(num)));
|
||||||
|
assert_eq!(String::from(str), String::from(JSONValue::from(str)));
|
||||||
|
assert_eq!(arr.clone(), JSONArray::from(JSONValue::from(arr.clone())));
|
||||||
|
assert_eq!(true, bool::from(JSONValue::from(true)));
|
||||||
|
assert_eq!(false, bool::from(JSONValue::from(false)));
|
||||||
|
assert_eq!((), <()>::from(JSONValue::from(())));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unwrap_array(self) -> Vec<JSONValue> {
|
#[test]
|
||||||
match self {
|
fn wrap_and_unwrap() {
|
||||||
JSONValue::Array(a) => a,
|
let map: JSONMap = HashMap::new();
|
||||||
_ => panic!("Tried to unwrap a non-array"),
|
let num = 9.380831539;
|
||||||
}
|
let str = "applesauce";
|
||||||
}
|
let arr: JSONArray = vec![JSONValue::from(map.clone()), JSONValue::from(num), JSONValue::from(str)];
|
||||||
|
|
||||||
|
let s: String = JSONValue::from(str).unwrap();
|
||||||
|
let a: JSONArray = JSONValue::from(arr.clone()).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(map.clone(), JSONValue::from(map.clone()).unwrap());
|
||||||
|
assert_eq!(num, JSONValue::from(num).unwrap());
|
||||||
|
assert_eq!(str, &s);
|
||||||
|
assert_eq!(arr.clone(), a);
|
||||||
|
assert_eq!(true, JSONValue::from(true).unwrap());
|
||||||
|
assert_eq!(false, JSONValue::from(false).unwrap());
|
||||||
|
assert_eq!((), JSONValue::from(()).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -426,10 +577,10 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn parse_string() {
|
fn parse_string() {
|
||||||
let res = JSON::new(r#""\t""#).parse_string();
|
let res = JSON::new(r#""\t""#).parse_string();
|
||||||
assert_eq!(res, Ok(Some(JSONValue::String(String::from("\t")))));
|
assert_eq!(res, Ok(Some(JSONValue::from("\t"))));
|
||||||
|
|
||||||
let res = JSON::new(r#""\u203d""#).parse_string();
|
let res = JSON::new(r#""\u203d""#).parse_string();
|
||||||
assert_eq!(res, Ok(Some(JSONValue::String(String::from("‽")))));
|
assert_eq!(res, Ok(Some(JSONValue::from("‽"))));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -468,7 +619,7 @@ mod tests {
|
|||||||
let result = JSON::new(r#"{"foo": "bar"}"#).parse_object();
|
let result = JSON::new(r#"{"foo": "bar"}"#).parse_object();
|
||||||
|
|
||||||
let mut hash_map: JSONMap = HashMap::new();
|
let mut hash_map: JSONMap = HashMap::new();
|
||||||
hash_map.insert(String::from("foo"), JSONValue::String(String::from("bar")));
|
hash_map.insert(String::from("foo"), JSONValue::from("bar"));
|
||||||
|
|
||||||
assert_eq!(result, Ok(Some(JSONValue::Object(hash_map))));
|
assert_eq!(result, Ok(Some(JSONValue::Object(hash_map))));
|
||||||
}
|
}
|
||||||
@ -491,7 +642,7 @@ mod tests {
|
|||||||
let res = JSON::parse(r#""/^$/""#);
|
let res = JSON::parse(r#""/^$/""#);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
res,
|
res,
|
||||||
Ok(JSONValue::String(String::from("/^$/"))),
|
Ok(JSONValue::from("/^$/")),
|
||||||
"Failed to parse string"
|
"Failed to parse string"
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -542,16 +693,16 @@ mod tests {
|
|||||||
let result =
|
let result =
|
||||||
JSON::parse(r#"["\"", "\\", "/", "\b", "\f", "\n", "\r", "\t", "\u0001", "\uface"]"#);
|
JSON::parse(r#"["\"", "\\", "/", "\b", "\f", "\n", "\r", "\t", "\u0001", "\uface"]"#);
|
||||||
let expected = Ok(Array(vec![
|
let expected = Ok(Array(vec![
|
||||||
JSONValue::String(String::from("\"")),
|
JSONValue::from("\""),
|
||||||
JSONValue::String(String::from("\\")),
|
JSONValue::from("\\"),
|
||||||
JSONValue::String(String::from("/")),
|
JSONValue::from("/"),
|
||||||
JSONValue::String(String::from("\u{8}")),
|
JSONValue::from("\u{8}"),
|
||||||
JSONValue::String(String::from("\x0C")),
|
JSONValue::from("\x0C"),
|
||||||
JSONValue::String(String::from("\n")),
|
JSONValue::from("\n"),
|
||||||
JSONValue::String(String::from("\r")),
|
JSONValue::from("\r"),
|
||||||
JSONValue::String(String::from("\t")),
|
JSONValue::from("\t"),
|
||||||
JSONValue::String(String::from("\u{1}")),
|
JSONValue::from("\u{1}"),
|
||||||
JSONValue::String(String::from("\u{face}")),
|
JSONValue::from("\u{face}"),
|
||||||
]));
|
]));
|
||||||
|
|
||||||
assert_eq!(result, expected);
|
assert_eq!(result, expected);
|
||||||
@ -591,27 +742,29 @@ mod tests {
|
|||||||
String::from("c"),
|
String::from("c"),
|
||||||
Array(vec![Number(1f64), Number(2f64), Number(3f64)]),
|
Array(vec![Number(1f64), Number(2f64), Number(3f64)]),
|
||||||
);
|
);
|
||||||
map.insert(String::from("d"), JSONValue::String(String::from("foo")));
|
map.insert(String::from("d"), JSONValue::from("foo"));
|
||||||
map.insert(String::from("e"), Object(emap));
|
map.insert(String::from("e"), Object(emap));
|
||||||
|
|
||||||
map.insert(
|
map.insert(
|
||||||
String::from("i"),
|
String::from("i"),
|
||||||
Array(vec![
|
Array(vec![
|
||||||
JSONValue::String(String::from("\"")),
|
JSONValue::from("\""),
|
||||||
JSONValue::String(String::from("\\")),
|
JSONValue::from("\\"),
|
||||||
JSONValue::String(String::from("/")),
|
JSONValue::from("/"),
|
||||||
JSONValue::String(String::from("\u{8}")),
|
JSONValue::from("\u{8}"),
|
||||||
JSONValue::String(String::from("\x0C")),
|
JSONValue::from("\x0C"),
|
||||||
JSONValue::String(String::from("\n")),
|
JSONValue::from("\n"),
|
||||||
JSONValue::String(String::from("\r")),
|
JSONValue::from("\r"),
|
||||||
JSONValue::String(String::from("\t")),
|
JSONValue::from("\t"),
|
||||||
JSONValue::String(String::from("\u{1}")),
|
JSONValue::from("\u{1}"),
|
||||||
JSONValue::String(String::from("\u{face}")),
|
JSONValue::from("\u{face}"),
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(result.is_ok(), format!("{:#?}", result));
|
assert!(result.is_ok(), format!("{:#?}", result));
|
||||||
let result_map = result.unwrap().unwrap_array()[0].clone().unwrap_object();
|
|
||||||
|
let outer_array: Vec<JSONValue> = result.unwrap().unwrap();
|
||||||
|
let result_map: JSONMap = outer_array[0].clone().unwrap();
|
||||||
|
|
||||||
for (k, v) in &map {
|
for (k, v) in &map {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
Loading…
Reference in New Issue
Block a user