Skip to content

Instantly share code, notes, and snippets.

@monadplus
Created November 21, 2022 22:02
Show Gist options
  • Save monadplus/1771cdcaaff2fd0c026f93bd377583e8 to your computer and use it in GitHub Desktop.
Save monadplus/1771cdcaaff2fd0c026f93bd377583e8 to your computer and use it in GitHub Desktop.
Rust: json macro
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