#![deny(
clippy::unwrap_used,
missing_debug_implementations,
missing_copy_implementations,
trivial_casts,
trivial_numeric_casts,
unsafe_code,
unstable_features,
unused_import_braces
)]
#[macro_use]
extern crate pest_derive;
use pest::iterators::Pair;
use pest::Parser;
use std::collections::HashMap;
use std::str::FromStr;
use thiserror::Error;
use unwrap::unwrap;
#[derive(Parser)]
#[grammar = "json_grammar.pest"]
struct JSONParser;
#[derive(Debug, PartialEq)]
pub enum JSONValue<'a, S: std::hash::BuildHasher> {
Object(HashMap<&'a str, JSONValue<'a, S>, S>),
Array(Vec<JSONValue<'a, S>>),
String(&'a str),
Number(Number),
Boolean(bool),
Null,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Number {
F64(f64),
U64(u64),
}
type JsonObject<'a, S> = HashMap<&'a str, JSONValue<'a, S>, S>;
impl<'a, S: std::hash::BuildHasher> JSONValue<'a, S> {
pub fn is_object(&self) -> bool {
if let JSONValue::Object(_) = self {
true
} else {
false
}
}
pub fn to_object(&self) -> Option<&HashMap<&'a str, JSONValue<'a, S>, S>> {
if let JSONValue::Object(object) = self {
Some(object)
} else {
None
}
}
pub fn is_array(&self) -> bool {
if let JSONValue::Array(_) = self {
true
} else {
false
}
}
pub fn to_array(&self) -> Option<&Vec<JSONValue<'a, S>>> {
if let JSONValue::Array(array) = self {
Some(array)
} else {
None
}
}
pub fn is_str(&self) -> bool {
if let JSONValue::String(_) = self {
true
} else {
false
}
}
pub fn to_str(&self) -> Option<&'a str> {
if let JSONValue::String(string) = self {
Some(string)
} else {
None
}
}
pub fn is_number(&self) -> bool {
if let JSONValue::Number(_) = self {
true
} else {
false
}
}
pub fn to_f64(&self) -> Option<f64> {
if let JSONValue::Number(number) = self {
match number {
Number::F64(f64_) => Some(*f64_),
Number::U64(u64_) => Some(*u64_ as f64),
}
} else {
None
}
}
pub fn to_u64(&self) -> Option<u64> {
if let JSONValue::Number(number) = self {
if let Number::U64(u64_) = number {
Some(*u64_)
} else {
None
}
} else {
None
}
}
pub fn is_bool(&self) -> bool {
if let JSONValue::Boolean(_) = self {
true
} else {
false
}
}
pub fn to_bool(&self) -> Option<bool> {
if let JSONValue::Boolean(boolean) = self {
Some(*boolean)
} else {
None
}
}
pub fn is_null(&self) -> bool {
if let JSONValue::Null = self {
true
} else {
false
}
}
}
impl<'a, S: std::hash::BuildHasher> ToString for JSONValue<'a, S> {
fn to_string(&self) -> String {
match self {
JSONValue::Object(o) => {
let contents: Vec<_> = o
.iter()
.map(|(name, value)| format!("\"{}\":{}", name, value.to_string()))
.collect();
format!("{{{}}}", contents.join(","))
}
JSONValue::Array(a) => {
let contents: Vec<_> = a.iter().map(Self::to_string).collect();
format!("[{}]", contents.join(","))
}
JSONValue::String(s) => format!("\"{}\"", s),
JSONValue::Number(n) => match n {
Number::F64(f64_) => format!("{}", f64_),
Number::U64(u64_) => format!("{}", u64_),
},
JSONValue::Boolean(b) => format!("{}", b),
JSONValue::Null => "null".to_owned(),
}
}
}
#[derive(Debug, Error)]
#[error("Fail to parse JSON String : {:?}", cause)]
pub struct ParseJsonError {
pub cause: String,
}
pub fn parse_json_string<'a>(
source: &'a str,
) -> Result<
JSONValue<'a, std::hash::BuildHasherDefault<std::collections::hash_map::DefaultHasher>>,
ParseJsonError,
> {
parse_json_string_with_specific_hasher::<
std::hash::BuildHasherDefault<std::collections::hash_map::DefaultHasher>,
>(source)
}
pub fn parse_json_string_with_specific_hasher<S: std::hash::BuildHasher + Default>(
source: &str,
) -> Result<JSONValue<S>, ParseJsonError> {
match JSONParser::parse(Rule::json, source) {
Ok(mut pair) => Ok(parse_value(unwrap!(
pair.next(),
"Fail to parse Rule::json"
))),
Err(pest_error) => Err(ParseJsonError {
cause: format!("{:?}", pest_error),
}),
}
}
fn parse_value<S: std::hash::BuildHasher + Default>(pair: Pair<Rule>) -> JSONValue<S> {
match pair.as_rule() {
Rule::object => JSONValue::Object(
pair.into_inner()
.map(|pair| {
let mut inner_rules = pair.into_inner();
let name = unwrap!(
unwrap!(inner_rules.next(), "Fail to parse Rule::object::name")
.into_inner()
.next(),
"Fail to parse Rule::object::name"
)
.as_str();
let value = parse_value(unwrap!(
inner_rules.next(),
"Fail to parse Rule::object::value"
));
(name, value)
})
.collect(),
),
Rule::array => JSONValue::Array(pair.into_inner().map(parse_value).collect()),
Rule::string => JSONValue::String(
unwrap!(pair.into_inner().next(), "Fail to parse Rule::string").as_str(),
),
Rule::number => {
if let Ok(number_u64) = u64::from_str(pair.as_str()) {
JSONValue::Number(Number::U64(number_u64))
} else {
JSONValue::Number(Number::F64(unwrap!(
pair.as_str().parse(),
"Fail to parse Rule::number as u64 and f64"
)))
}
}
Rule::boolean => JSONValue::Boolean(unwrap!(
pair.as_str().parse(),
"Fail to parse Rule::boolean"
)),
Rule::null => JSONValue::Null,
Rule::json
| Rule::EOI
| Rule::pair
| Rule::value
| Rule::inner_string
| Rule::char
| Rule::WHITESPACE => unreachable!(),
}
}
pub fn get_optional_usize<S: std::hash::BuildHasher>(
json_block: &HashMap<&str, JSONValue<S>, S>,
field: &str,
) -> Result<Option<usize>, ParseJsonError> {
Ok(match json_block.get(field) {
Some(value) => {
if !value.is_null() {
Some(
value
.to_f64()
.ok_or_else(|| ParseJsonError {
cause: format!(
"Fail to parse json : field '{}' must be a number !",
field
),
})?
.trunc() as usize,
)
} else {
None
}
}
None => None,
})
}
pub fn get_optional_str_not_empty<'a, S: std::hash::BuildHasher>(
json_block: &'a HashMap<&str, JSONValue<S>, S>,
field: &str,
) -> Result<Option<&'a str>, ParseJsonError> {
let result = get_optional_str(json_block, field);
if let Ok(Some(value)) = result {
if !value.is_empty() {
Ok(Some(value))
} else {
Ok(None)
}
} else {
result
}
}
pub fn get_optional_str<'a, S: std::hash::BuildHasher>(
json_block: &'a HashMap<&str, JSONValue<S>, S>,
field: &str,
) -> Result<Option<&'a str>, ParseJsonError> {
Ok(match json_block.get(field) {
Some(value) => {
if !value.is_null() {
Some(value.to_str().ok_or_else(|| ParseJsonError {
cause: format!("Fail to parse json : field '{}' must be a string !", field),
})?)
} else {
None
}
}
None => None,
})
}
pub fn get_u64<S: std::hash::BuildHasher>(
json_block: &HashMap<&str, JSONValue<S>, S>,
field: &str,
) -> Result<u64, ParseJsonError> {
Ok(json_block
.get(field)
.ok_or_else(|| ParseJsonError {
cause: format!("Fail to parse json : field '{}' must exist !", field),
})?
.to_u64()
.ok_or_else(|| ParseJsonError {
cause: format!("Fail to parse json : field '{}' must be a number !", field),
})?)
}
pub fn get_number<S: std::hash::BuildHasher>(
json_block: &HashMap<&str, JSONValue<S>, S>,
field: &str,
) -> Result<f64, ParseJsonError> {
Ok(json_block
.get(field)
.ok_or_else(|| ParseJsonError {
cause: format!("Fail to parse json : field '{}' must exist !", field),
})?
.to_f64()
.ok_or_else(|| ParseJsonError {
cause: format!("Fail to parse json : field '{}' must be a number !", field),
})?)
}
pub fn get_str<'a, S: std::hash::BuildHasher>(
json_block: &'a HashMap<&str, JSONValue<S>, S>,
field: &str,
) -> Result<&'a str, ParseJsonError> {
Ok(json_block
.get(field)
.ok_or_else(|| ParseJsonError {
cause: format!("Fail to parse json : field '{}' must exist !", field),
})?
.to_str()
.ok_or_else(|| ParseJsonError {
cause: format!("Fail to parse json : field '{}' must be a string !", field),
})?)
}
pub fn get_str_array<'a, S: std::hash::BuildHasher>(
json_block: &'a HashMap<&str, JSONValue<S>, S>,
field: &str,
) -> Result<Vec<&'a str>, ParseJsonError> {
json_block
.get(field)
.ok_or_else(|| ParseJsonError {
cause: format!("Fail to parse json : field '{}' must exist !", field),
})?
.to_array()
.ok_or_else(|| ParseJsonError {
cause: format!("Fail to parse json : field '{}' must be an array !", field),
})?
.iter()
.map(|v| {
v.to_str().ok_or_else(|| ParseJsonError {
cause: format!(
"Fail to parse json : field '{}' must be an array of string !",
field
),
})
})
.collect()
}
pub fn get_array<'a, S: std::hash::BuildHasher>(
json_block: &'a HashMap<&str, JSONValue<S>, S>,
field: &str,
) -> Result<Vec<&'a JSONValue<'a, S>>, ParseJsonError> {
Ok(json_block
.get(field)
.ok_or_else(|| ParseJsonError {
cause: format!("Fail to parse json : field '{}' must exist !", field),
})?
.to_array()
.ok_or_else(|| ParseJsonError {
cause: format!("Fail to parse json : field '{}' must be an array !", field),
})?
.iter()
.map(|v| v)
.collect())
}
pub fn get_object_array<'a, S: std::hash::BuildHasher>(
json_block: &'a JsonObject<'a, S>,
field: &str,
) -> Result<Vec<&'a JsonObject<'a, S>>, ParseJsonError> {
json_block
.get(field)
.ok_or_else(|| ParseJsonError {
cause: format!("Fail to parse json : field '{}' must exist !", field),
})?
.to_array()
.ok_or_else(|| ParseJsonError {
cause: format!("Fail to parse json : field '{}' must be an array !", field),
})?
.iter()
.map(|v| {
v.to_object().ok_or_else(|| ParseJsonError {
cause: format!("Fail to parse json : field '{}' must be an object !", field),
})
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_parse_too_large_number() {
assert_eq!(
Ok(100_010_200_000_006_940),
u64::from_str("100010200000006940"),
);
let json_string = "{
\"nonce\": 100010200000006940
}";
let json_value = parse_json_string(json_string).expect("Fail to parse json string !");
assert!(json_value.is_object());
let json_object = json_value.to_object().expect("safe unwrap");
assert_eq!(
json_object.get("nonce"),
Some(&JSONValue::Number(Number::U64(100_010_200_000_006_940)))
);
}
#[test]
fn test_parse_wrong_json_string() {
let json_string = "{";
assert!(parse_json_string(json_string).is_err());
}
fn test_parse_json_string_check_object_type(
json_value: &JSONValue<
std::hash::BuildHasherDefault<std::collections::hash_map::DefaultHasher>,
>,
) {
assert!(json_value.is_object());
assert!(!json_value.is_array());
assert!(!json_value.is_str());
assert!(!json_value.is_number());
assert!(!json_value.is_bool());
assert!(!json_value.is_null());
assert_eq!(None, json_value.to_array());
assert_eq!(None, json_value.to_str());
assert_eq!(None, json_value.to_f64());
assert_eq!(None, json_value.to_u64());
assert_eq!(None, json_value.to_bool());
}
#[test]
fn test_parse_json_string() {
let json_string = "{
\"name\": \"toto\",
\"age\": 25,
\"legalAge\": true,
\"ratio\": 0.5,
\"friends\": [
\"titi\",
\"tata\"
],
\"car\": null
}";
let json_value = parse_json_string(json_string).expect("Fail to parse json string !");
assert_eq!(
json_value.to_string(),
"{\"name\":\"toto\",\"legalAge\":true,\"ratio\":0.5,\"age\":25,\"friends\":[\"titi\",\"tata\"],\"car\":null}"
);
test_parse_json_string_check_object_type(&json_value);
let json_object = json_value.to_object().expect("safe unwrap");
let name_field = json_object.get("name").expect("name field must be exist");
assert!(name_field.is_str());
assert_eq!(name_field, &JSONValue::String("toto"));
let age_field = json_object.get("age").expect("age field must be exist");
assert!(age_field.is_number());
assert_eq!(age_field.to_f64(), Some(25.0f64));
assert_eq!(age_field.to_u64(), Some(25u64));
let legal_age_field = json_object
.get("legalAge")
.expect("legalAge field must be exist");
assert!(legal_age_field.is_bool());
assert_eq!(legal_age_field.to_bool(), Some(true));
let ratio_field = json_object.get("ratio").expect("ratio field must be exist");
assert!(ratio_field.is_number());
assert_eq!(ratio_field.to_f64(), Some(0.5f64));
assert_eq!(ratio_field.to_u64(), None);
let friends_field = json_object
.get("friends")
.expect("friends field must be exist");
assert!(!friends_field.is_object());
assert_eq!(None, friends_field.to_object());
assert!(friends_field.is_array());
let friends = friends_field
.to_array()
.expect("frinds_field must be an array");
assert_eq!(2, friends.len());
assert_eq!(
"titi",
friends[0]
.to_str()
.expect("friends field must be an array of String")
);
assert_eq!(
"tata",
friends[1]
.to_str()
.expect("friends field must be an array of String")
);
let car_field = json_object.get("car").expect("car field must be exist");
assert!(car_field.is_null());
}
}