use nom::{ IResult, Parser, branch::alt, bytes::complete::{tag, take_till1}, character::complete::{multispace0, one_of}, combinator::value, multi::separated_list0, number::complete::double, sequence::delimited, }; use std::collections::HashMap; #[derive(PartialEq, Debug, Clone)] pub enum JsonValue { Object(HashMap), Array(Vec), String(String), Float(f64), Integer(i64), Boolean(bool), Null, } fn parse_null(input: &str) -> IResult<&str, JsonValue> { value(JsonValue::Null, tag("null")).parse(input) } fn parse_bool(input: &str) -> IResult<&str, JsonValue> { alt((value(true, tag("true")), value(false, tag("false")))) .map(JsonValue::Boolean) .parse(input) } fn parse_string(input: &str) -> IResult<&str, String> { delimited(tag("\""), parse_string_inner, tag("\"")).parse(input) } fn parse_string_inner(input: &str) -> IResult<&str, String> { let mut string_chunks = vec![]; let mut remaining = input; while let Ok((rem, parsed)) = alt(( parse_string_inner_naive.map(|s| s.to_string()), parse_escape_character.map(|s| s.to_string()), )) .parse(remaining) { remaining = rem; string_chunks.push(parsed) } Ok((remaining, string_chunks.concat())) } fn parse_string_inner_naive(input: &str) -> IResult<&str, &str> { take_till1(|c: char| c == '\"' || c == '\\' || c.is_control())(input) } fn parse_escape_character(input: &str) -> IResult<&str, char> { alt(( value('\"', tag("\\\"")), // value('\f', tag("\\f")), value('\\', tag("\\\\")), value('\n', tag("\\n")), value('/', tag("\\/")), value('\r', tag("\\r")), // value('\b', tag("\\b")), value('\t', tag("\\t")), //todo: unicode )) .parse(input) } fn parse_array(input: &str) -> IResult<&str, JsonValue> { delimited( tag("["), separated_list0(tag(","), deserialize_json), tag("]"), ) .map(JsonValue::Array) .parse(input) } fn parse_object(input: &str) -> IResult<&str, JsonValue> { delimited(tag("{"), separated_list0(tag(","), parse_pair), tag("}")) .map(|kvps| kvps.into_iter().collect::>()) .map(JsonValue::Object) .parse(input) } fn parse_pair(input: &str) -> IResult<&str, (String, JsonValue)> { ( delimited(multispace0, parse_string, multispace0), tag(":"), deserialize_json, ) .map(|(k, _, v)| (k, v)) .parse(input) } fn parse_integer(input: &str) -> IResult<&str, JsonValue> { ( nom::character::complete::i64, nom::combinator::not(one_of("eE.")), ) .map(|(i, _)| JsonValue::Integer(i)) .parse(input) } pub fn deserialize_json(json: &str) -> IResult<&str, JsonValue> { // map_res(); delimited( multispace0, alt(( parse_null, parse_bool, parse_integer, double.map(JsonValue::Float), parse_string.map(JsonValue::String), parse_array, parse_object, )), multispace0, ) .parse(json) } #[cfg(test)] mod tests { use super::*; #[test] fn deserializes_null() { let null = deserialize_json("null").unwrap(); assert_eq!(null.1, JsonValue::Null) } #[test] fn deserializes_booleans() { let t = deserialize_json("true").unwrap(); let f = deserialize_json("false").unwrap(); assert_eq!(t.1, JsonValue::Boolean(true)); assert_eq!(f.1, JsonValue::Boolean(false)); } #[test] fn deserializes_strings() { let t = deserialize_json(r#""true""#).unwrap(); assert_eq!(t.1, JsonValue::String("true".to_string())); let f = deserialize_json(r#""false""#).unwrap(); assert_eq!(f.1, JsonValue::String("false".to_string())); } #[test] fn deserializes_escaped_strings() { let escaped = deserialize_json(r#"" \" \\ \n \/ \r \t ""#).unwrap(); assert_eq!( escaped.1, JsonValue::String(" \" \\ \n / \r \t ".to_string()) ); } #[test] fn invalid_escaped_strings() { deserialize_json(r#"" \g ""#).expect_err("should be an error"); } #[test] fn deserializes_simple_arrays() { let t = deserialize_json("[]").unwrap(); assert_eq!(t.1, JsonValue::Array(vec![])); let f = deserialize_json("[true]").unwrap(); assert_eq!(f.1, JsonValue::Array(vec![JsonValue::Boolean(true)])); } #[test] fn deserializes_arrays() { let f = deserialize_json("[true,false]").unwrap(); assert_eq!( f.1, JsonValue::Array(vec![JsonValue::Boolean(true), JsonValue::Boolean(false)]) ); } #[test] fn deserializes_nested_arrays() { let f = deserialize_json("[[true,false],[\"string!\"],[[[null],[],[]]]]").unwrap(); let empty_array = JsonValue::Array(vec![]); let boolean_array = JsonValue::Array(vec![JsonValue::Boolean(true), JsonValue::Boolean(false)]); let three_arrays = JsonValue::Array(vec![ JsonValue::Array(vec![JsonValue::Null]), empty_array.clone(), empty_array.clone(), ]); assert_eq!( f.1, JsonValue::Array(vec![ boolean_array, JsonValue::Array(vec![JsonValue::String("string!".to_string())]), JsonValue::Array(vec![three_arrays]) ]) ); } #[test] fn deserializes_simple_objects() { let t = deserialize_json("{}").unwrap(); assert_eq!(t.1, JsonValue::Object(HashMap::new())); let f = deserialize_json("{\"\":[]}").unwrap(); assert_eq!( f.1, JsonValue::Object( vec![(String::new(), JsonValue::Array(vec![]))] .into_iter() .collect() ) ); } #[test] fn deserializes_object() { let result = deserialize_json(r#"{"includeLikes":true,"includeShares":true,"includeFollows":true,"includeReplies":true,"includeComments":true}"#).unwrap(); let JsonValue::Object(hm) = result.1 else { panic!() }; assert_eq!(hm.get("includeLikes"), Some(&JsonValue::Boolean(true))); assert_eq!(hm.get("includeShares"), Some(&JsonValue::Boolean(true))); assert_eq!(hm.get("includeFollows"), Some(&JsonValue::Boolean(true))); assert_eq!(hm.get("includeReplies"), Some(&JsonValue::Boolean(true))); } #[test] fn deserializes_real_json() { let result = deserialize_json(include_str!("../data/user.json")).unwrap(); let JsonValue::Object(hm) = result.1 else { panic!() }; assert_eq!(hm.get("userId"), Some(&JsonValue::Integer(1234))) } #[test] fn deserializes_whitespaced_json() { let result = deserialize_json(include_str!("../data/whitespaced_user.json")).unwrap(); let JsonValue::Object(hm) = result.1 else { panic!() }; assert_eq!(hm.get("userId"), Some(&JsonValue::Integer(1234))) } }