use inflections::Inflect;
use quote::{format_ident, quote, ToTokens};
use serde_json::{Map, Value};
use syn::Ident;
pub struct JsonMacroInput {
pub struct_name: Ident,
pub content: Value,
}
pub fn generate_structs(
json_struct: &JsonMacroInput,
base_name: &Ident,
) -> (proc_macro2::TokenStream, Vec<proc_macro2::TokenStream>) {
let mut all_structs = Vec::new();
let mut fields = Vec::new();
let content = match json_struct.content.as_object() {
Some(obj) => obj,
None => &Map::new(),
};
for (key, value) in content {
if key.eq("struct_name") {
continue;
}
let key = key.to_snake_case();
let field_name = format_ident!("{}", key);
let field_type = match value {
Value::String(_) => quote!(String),
Value::Number(_) => quote!(f64),
Value::Bool(_) => quote!(bool),
Value::Array(arr) => {
let (elem_type, _) = infer_array_type(arr);
quote!(Vec<#elem_type>)
}
Value::Object(obj) => {
let nested_name = {
if let Some(struct_name_value) = obj.get("struct_name") {
if let Value::String(struct_name) = struct_name_value {
if struct_name.eq("key") {
format_ident!("{}", key.to_pascal_case())
} else {
format_ident!("{}", struct_name.to_pascal_case())
}
} else {
unreachable!()
}
} else {
format_ident!("{}{}", base_name, key.to_pascal_case())
}
};
let nested_macro_input = JsonMacroInput {
struct_name: json_struct.struct_name.clone(),
content: Value::Object(obj.clone()),
};
let (nested_struct, nested_structs) =
generate_structs(&nested_macro_input, &nested_name);
all_structs.extend(nested_structs);
all_structs.push(nested_struct.clone());
format_ident!("{}", nested_name).into_token_stream()
}
Value::Null => quote!(Option<::serde_json::Value>),
};
let field = quote! {
#[serde(alias = #key)]
pub #field_name: #field_type
};
fields.push(field);
}
let main_struct = quote! {
#[derive(::serde::Deserialize, ::serde::Serialize, ::std::clone::Clone, ::std::fmt::Debug, ::std::default::Default)]
#[serde(rename_all = "camelCase")]
pub struct #base_name {
#(#fields),*
}
};
(main_struct, all_structs)
}
fn infer_array_type(arr: &[Value]) -> (proc_macro2::TokenStream, Vec<proc_macro2::TokenStream>) {
if arr.is_empty() {
return (quote!(::serde_json::Value), Vec::new());
}
match &arr[0] {
Value::String(_) => (quote!(String), Vec::new()),
Value::Number(_) => (quote!(f64), Vec::new()),
Value::Bool(_) => (quote!(bool), Vec::new()),
_ => (quote!(::serde_json::Value), Vec::new()),
}
}