From 97bfd16b323edb756b4735b432b4429f60f24aff Mon Sep 17 00:00:00 2001 From: "Timothy J. Warren" Date: Tue, 11 Feb 2020 19:12:01 -0500 Subject: [PATCH] Fun with traits for better ergonomics of the JSONValue Enum --- src/lib.rs | 239 +++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 196 insertions(+), 43 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index b39d4f2..4a3b3b5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,7 @@ use std::iter::FromIterator; use std::{char, u16}; pub type JSONResult = Result; +pub type JSONArray = Vec; pub type JSONMap = HashMap; /// The type of JSON value @@ -34,6 +35,139 @@ pub enum JSONValue { 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>(self) -> T { + T::from(self) + } +} + +impl From 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 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 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 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 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 for () { + /// This will just swallow the enum value and return a unit tuple + fn from(_: JSONValue) -> () { () } +} + +impl From for JSONValue { + /// Wraps the `HashMap` in the `JSONValue` enum + fn from(val: JSONMap) -> JSONValue { + Self::Object(val) + } +} + +impl From for JSONValue { + /// Wraps the `Vec` in the `JSONValue` enum + fn from(val: JSONArray) -> JSONValue { + Self::Array(val) + } +} + +impl From 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 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 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 #[derive(Debug, PartialEq)] pub enum ParseError { @@ -111,9 +245,7 @@ impl JSON { ); // Every parser failed, so the syntax is probably incorrect - Err(ParseError::UnexpectedEndOfInput(String::from( - "Doesn't seem to be valid JSON", - ))) + Err(ParseError::UnexpectedEndOfInput(format!("Doesn't seem to be valid JSON"))) } /// See if there's a `JSONValue::Object` next in the JSON @@ -315,7 +447,7 @@ impl JSON { match str.parse::() { Ok(number) => { self.increment(str.len()); - return Ok(Some(JSONValue::Number(number))); + return Ok(Some(JSONValue::from(number))); } 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::*; - impl JSONValue { - fn unwrap_object(self) -> JSONMap { - match self { - JSONValue::Object(o) => o, - _ => panic!("Tried to unwrap a non-object"), - } - } + #[test] + fn value_conversion() { + let map: JSONMap = HashMap::new(); + let num = 9.380831539; + let str = "applesauce"; + let arr: JSONArray = vec![JSONValue::from(map.clone()), JSONValue::from(num), JSONValue::from(str)]; - fn unwrap_array(self) -> Vec { - match self { - JSONValue::Array(a) => a, - _ => panic!("Tried to unwrap a non-array"), - } - } + 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(()))); + } + + #[test] + fn wrap_and_unwrap() { + let map: JSONMap = HashMap::new(); + 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] @@ -426,10 +577,10 @@ mod tests { #[test] fn 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(); - assert_eq!(res, Ok(Some(JSONValue::String(String::from("‽"))))); + assert_eq!(res, Ok(Some(JSONValue::from("‽")))); } #[test] @@ -468,7 +619,7 @@ mod tests { let result = JSON::new(r#"{"foo": "bar"}"#).parse_object(); 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)))); } @@ -491,7 +642,7 @@ mod tests { let res = JSON::parse(r#""/^$/""#); assert_eq!( res, - Ok(JSONValue::String(String::from("/^$/"))), + Ok(JSONValue::from("/^$/")), "Failed to parse string" ); @@ -542,16 +693,16 @@ mod tests { let result = JSON::parse(r#"["\"", "\\", "/", "\b", "\f", "\n", "\r", "\t", "\u0001", "\uface"]"#); let expected = Ok(Array(vec![ - JSONValue::String(String::from("\"")), - JSONValue::String(String::from("\\")), - JSONValue::String(String::from("/")), - JSONValue::String(String::from("\u{8}")), - JSONValue::String(String::from("\x0C")), - JSONValue::String(String::from("\n")), - JSONValue::String(String::from("\r")), - JSONValue::String(String::from("\t")), - JSONValue::String(String::from("\u{1}")), - JSONValue::String(String::from("\u{face}")), + JSONValue::from("\""), + JSONValue::from("\\"), + JSONValue::from("/"), + JSONValue::from("\u{8}"), + JSONValue::from("\x0C"), + JSONValue::from("\n"), + JSONValue::from("\r"), + JSONValue::from("\t"), + JSONValue::from("\u{1}"), + JSONValue::from("\u{face}"), ])); assert_eq!(result, expected); @@ -591,27 +742,29 @@ mod tests { String::from("c"), 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("i"), Array(vec![ - JSONValue::String(String::from("\"")), - JSONValue::String(String::from("\\")), - JSONValue::String(String::from("/")), - JSONValue::String(String::from("\u{8}")), - JSONValue::String(String::from("\x0C")), - JSONValue::String(String::from("\n")), - JSONValue::String(String::from("\r")), - JSONValue::String(String::from("\t")), - JSONValue::String(String::from("\u{1}")), - JSONValue::String(String::from("\u{face}")), + JSONValue::from("\""), + JSONValue::from("\\"), + JSONValue::from("/"), + JSONValue::from("\u{8}"), + JSONValue::from("\x0C"), + JSONValue::from("\n"), + JSONValue::from("\r"), + JSONValue::from("\t"), + JSONValue::from("\u{1}"), + JSONValue::from("\u{face}"), ]), ); assert!(result.is_ok(), format!("{:#?}", result)); - let result_map = result.unwrap().unwrap_array()[0].clone().unwrap_object(); + + let outer_array: Vec = result.unwrap().unwrap(); + let result_map: JSONMap = outer_array[0].clone().unwrap(); for (k, v) in &map { assert_eq!(