Created
November 21, 2022 22:02
-
-
Save monadplus/1771cdcaaff2fd0c026f93bd377583e8 to your computer and use it in GitHub Desktop.
Rust: json macro
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::collections::BTreeMap; | |
trait ToValue { | |
fn to_value(self) -> Value; | |
} | |
impl ToValue for bool { | |
fn to_value(self) -> Value { | |
Value::Bool(self) | |
} | |
} | |
impl ToValue for &str { | |
fn to_value(self) -> Value { | |
Value::String(self.to_string()) | |
} | |
} | |
impl ToValue for String { | |
fn to_value(self) -> Value { | |
Value::String(self) | |
} | |
} | |
impl ToValue for i32 { | |
fn to_value(self) -> Value { | |
Value::Number(self as f64) | |
} | |
} | |
impl ToValue for u32 { | |
fn to_value(self) -> Value { | |
Value::Number(self as f64) | |
} | |
} | |
impl ToValue for u64 { | |
fn to_value(self) -> Value { | |
Value::Number(self as f64) | |
} | |
} | |
impl ToValue for f32 { | |
fn to_value(self) -> Value { | |
Value::Number(self as f64) | |
} | |
} | |
impl ToValue for f64 { | |
fn to_value(self) -> Value { | |
Value::Number(self) | |
} | |
} | |
#[derive(Debug, Clone, PartialEq)] | |
pub enum Value { | |
Null, | |
Bool(bool), | |
String(String), | |
Number(f64), | |
Array(Vec<Value>), | |
Object(BTreeMap<String, Value>), | |
} | |
#[macro_export] | |
macro_rules! json { | |
//////////////////////////////////////////// | |
// Array | |
//////////////////////////////////////////// | |
// Base case | |
(@array [$($elems:expr),* $(,)?]) => { | |
vec![$($elems),*] | |
}; | |
(@array [$($elems:expr),*] []) => { | |
json!(@array [$($elems,)*]) | |
}; | |
// Parse last element and go to base case | |
(@array [$($elems:expr),*] [$last:tt]) => { | |
json!(@array [$($elems,)* json!($last)]) | |
}; | |
// Parse value, keep processing | |
(@array [$($elems:expr),*] [$head:tt, $($tail:tt)*]) => { | |
json!(@array [ $($elems,)* json!($head) ] [ $($tail)* ]) | |
}; | |
//////////////////////////////////////////// | |
// Object | |
//////////////////////////////////////////// | |
// Base case | |
(@object $object:ident () ()) => { | |
}; | |
// Insert KV | |
(@object $object:ident [$($key:tt)+] ($value:expr, $($rest:tt)*)) => { | |
let _ = $object.insert(($($key)+).into(), $value); | |
json!(@object $object () ($($rest)*)); | |
}; | |
// Insert KV (last) | |
(@object $object:ident [$($key:tt)+] ($value:expr)) => { | |
let _ = $object.insert(($($key)+).into(), $value); | |
}; | |
// Parse value | |
(@object $object:ident ($($key:tt)+) (: $value:tt , $($rest:tt)*)) => { | |
json!(@object $object [$($key)+] (json!($value), $($rest)*)); | |
}; | |
// Parse value (last) | |
(@object $object:ident ($($key:tt)+) (: $value:tt)) => { | |
json!(@object $object [$($key)+] (json!($value))); | |
}; | |
// Parse key | |
(@object $object:ident () ($key:literal : $($rest:tt)*)) => { | |
json!(@object $object ($key) (: $($rest)*)); | |
}; | |
//////////////////////////////////////////// | |
// Base | |
//////////////////////////////////////////// | |
(null) => { | |
$crate::Value::Null | |
}; | |
// Empty array | |
([]) => { | |
$crate::Value::Array(json!(@array [])) | |
}; | |
// Array | |
([ $($tt:tt)+ ]) => { | |
$crate::Value::Array(json!(@array [] [$($tt)+])) | |
}; | |
// Empty object | |
({}) => { | |
$crate::Value::Object(::std::collections::BTreeMap::new()) | |
}; | |
// Object | |
({ $($tt:tt)+ }) => { | |
$crate::Value::Object({ | |
let mut object = ::std::collections::BTreeMap::new(); | |
json!(@object object () ($($tt)+)); | |
object | |
}) | |
}; | |
// Bool, Number and String | |
($other:expr) => { | |
$crate::ToValue::to_value($other) | |
}; | |
} | |
/// When a token is unexpected, this will produce a nice error. | |
/// | |
/// ```ignore | |
/// json_unexpected!{$colon} | |
/// ``` | |
#[allow(unused_macros)] | |
macro_rules! json_unexpected { | |
() => {}; | |
} | |
#[test] | |
fn json_test() { | |
// Null | |
assert_eq!(json!(null), Value::Null); | |
// Bool | |
assert_eq!(json!(false), Value::Bool(false)); | |
assert_eq!(json!(true), Value::Bool(true)); | |
// String | |
assert_eq!(json!("Hello"), Value::String("Hello".to_string())); | |
assert_eq!( | |
json!("Hello".to_string()), | |
Value::String("Hello".to_string()) | |
); | |
// Number | |
assert_eq!(json!(0), Value::Number(0.0_f64)); | |
assert_eq!(json!(99), Value::Number(99_f64)); | |
assert_eq!(json!(0.0), Value::Number(0.0_f64)); | |
assert_eq!(json!(99.99), Value::Number(99.99_f64)); | |
// Array | |
assert_eq!(json!([]), Value::Array(vec![])); | |
assert_eq!(json!([null]), Value::Array(vec![Value::Null])); | |
assert_eq!( | |
json!([[null]]), | |
Value::Array(vec! {Value::Array(vec![Value::Null])}) | |
); | |
assert_eq!( | |
json!([[null], [true]]), | |
Value::Array(vec! { | |
Value::Array(vec![Value::Null]), | |
Value::Array(vec![Value::Bool(true)]), | |
}) | |
); | |
assert_eq!(json!([true]), Value::Array(vec![Value::Bool(true)])); | |
assert_eq!(json!([true,]), Value::Array(vec![Value::Bool(true)])); | |
assert_eq!( | |
json!([true, null]), | |
Value::Array(vec![Value::Bool(true), Value::Null]) | |
); | |
assert_eq!( | |
json!([true, "hello", 12.5]), | |
Value::Array(vec![Value::Bool(true), "hello".to_value(), 12.5.to_value()]) | |
); | |
// Object | |
assert_eq!(json!({}), Value::Object(BTreeMap::new())); | |
let mut object = BTreeMap::new(); | |
object.insert("name".into(), Value::String("arnau".into())); | |
let expected = Value::Object(object); | |
let result = json!({ | |
"name" : "arnau" | |
}); | |
assert_eq!(expected, result); | |
let mut object = BTreeMap::new(); | |
object.insert("name".into(), Value::String("arnau".into())); | |
object.insert("pet".into(), Value::String("dog".into())); | |
let expected = Value::Object(object); | |
let result = json!({ | |
"name" : "arnau", | |
"pet" : "dog" | |
}); | |
assert_eq!(result, expected); | |
let result = json!({ | |
"name" : "arnau", | |
"pet" : "dog", | |
}); | |
assert_eq!(result, expected); | |
let mut object = BTreeMap::new(); | |
object.insert("name".into(), Value::String("arnau".into())); | |
object.insert( | |
"arr".into(), | |
Value::Array(vec![Value::Bool(true), Value::Null]), | |
); | |
let expected = Value::Object(object); | |
let result = json!({ | |
"name" : "arnau", | |
"arr" : [ | |
true, | |
null | |
] | |
}); | |
assert_eq!(result, expected); | |
let mut object = BTreeMap::new(); | |
object.insert("name".into(), Value::String("arnau".into())); | |
object.insert( | |
"arr".into(), | |
Value::Array(vec![Value::Bool(true), Value::Null]), | |
); | |
object.insert( | |
"obj".into(), | |
Value::Object({ | |
let mut object = BTreeMap::new(); | |
object.insert("name".into(), Value::String("arnau".into())); | |
object.insert("pet".into(), Value::String("dog".into())); | |
object | |
}), | |
); | |
let expected = Value::Object(object); | |
let result = json!({ | |
"name" : "arnau", | |
"arr" : [ | |
true, | |
null, | |
], | |
"obj" : { | |
"name": "arnau", | |
"pet": "dog", | |
}, | |
}); | |
assert_eq!(result, expected); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment