如何实现Rust结构体Schema反射,生成含类型与注释的JSON?
在Rust中实现结构体反射并生成带注释的JSON
Rust本身没有原生运行时反射,但可以通过过程宏实现这类需求,下面是两种通用解决方案:
方案一:自定义过程宏自动提取元数据
通过解析结构体的抽象语法树(AST),自动提取字段名、类型和文档注释,生成对应的JSON元数据。
实现步骤
- 定义带文档注释的目标结构体:
/// 关于接口的响应结构体 #[derive(Debug)] struct AboutRsp { /// 服务名称 service_name: String, /// 当前服务版本 version: String, /// 服务启动时间戳 start_time: u64, }
- 编写过程宏解析AST:
use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, DeriveInput, Fields}; #[proc_macro_derive(StructMetadata)] pub fn derive_struct_metadata(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let struct_name = &input.ident; // 提取结构体的文档注释 let struct_desc = input.attrs.iter() .filter_map(|attr| attr.parse_meta().ok()) .filter(|meta| meta.path().is_ident("doc")) .filter_map(|meta| match meta { syn::Meta::NameValue(nv) => match nv.lit { syn::Lit::Str(s) => Some(s.value()), _ => None, }, _ => None, }) .collect::<Vec<_>>() .join("\n"); // 处理结构体字段 let fields = match input.data { syn::Data::Struct(s) => match s.fields { Fields::Named(fields) => fields.named, _ => panic!("仅支持具名字段的结构体"), }, _ => panic!("仅支持结构体类型"), }; let field_meta = fields.iter().map(|field| { let field_name = field.ident.as_ref().unwrap().to_string(); let field_type = quote!(#field.ty).to_string(); // 提取字段的文档注释 let field_desc = field.attrs.iter() .filter_map(|attr| attr.parse_meta().ok()) .filter(|meta| meta.path().is_ident("doc")) .filter_map(|meta| match meta { syn::Meta::NameValue(nv) => match nv.lit { syn::Lit::Str(s) => Some(s.value()), _ => None, }, _ => None, }) .collect::<Vec<_>>() .join("\n"); quote! { serde_json::json!({ "field_name": #field_name, "type": #field_type, "description": #field_desc }) } }); // 生成扩展代码 let expanded = quote! { impl #struct_name { pub fn metadata() -> serde_json::Value { serde_json::json!({ "struct_name": stringify!(#struct_name), "description": #struct_desc, "fields": vec![#(#field_meta),*] }) } } }; expanded.into() }
- 派生宏并生成JSON:
#[derive(StructMetadata)] /// 关于接口的响应结构体 struct AboutRsp { /// 服务名称 service_name: String, /// 当前服务版本 version: String, /// 服务启动时间戳 start_time: u64, } fn main() { let meta = AboutRsp::metadata(); println!("{}", serde_json::to_string_pretty(&meta).unwrap()); }
运行后输出的JSON格式如下:
{ "struct_name": "AboutRsp", "description": "关于接口的响应结构体", "fields": [ { "field_name": "service_name", "type": "String", "description": "服务名称" }, { "field_name": "version", "type": "String", "description": "当前服务版本" }, { "field_name": "start_time", "type": "u64", "description": "服务启动时间戳" } ] }
方案二:手动绑定元数据(轻量场景)
如果不想编写复杂宏,可以手动维护结构体和对应的元数据结构,适合小型项目:
use serde::Serialize; /// 关于接口的响应结构体 #[derive(Debug, Serialize)] struct AboutRsp { service_name: String, version: String, start_time: u64, } #[derive(Serialize)] struct FieldMeta { field_name: &'static str, r#type: &'static str, description: &'static str, } #[derive(Serialize)] struct StructMeta { struct_name: &'static str, description: &'static str, fields: Vec<FieldMeta>, } impl AboutRsp { pub fn metadata() -> StructMeta { StructMeta { struct_name: "AboutRsp", description: "关于接口的响应结构体", fields: vec![ FieldMeta { field_name: "service_name", r#type: "String", description: "服务名称", }, FieldMeta { field_name: "version", r#type: "String", description: "当前服务版本", }, FieldMeta { field_name: "start_time", r#type: "u64", description: "服务启动时间戳", }, ], } } } fn main() { let meta = AboutRsp::metadata(); println!("{}", serde_json::to_string_pretty(&meta).unwrap()); }
核心说明
- 过程宏是实现自动提取的关键,能在编译阶段解析结构体的所有静态信息
- 文档注释在AST中以
#[doc = "..."]属性存在,需过滤提取字符串内容 - 类型字符串可通过
quote!将类型Token转换为文本,复杂类型可借助syn的类型打印功能优化
内容的提问来源于stack exchange,提问作者Jamie
相关产品推荐
相关产品推荐

