json_to_struct/lib.rs
1//! # json2struct: Compile-Time Struct Generation
2//!
3//! A powerful procedural macro for generating Rust structs from JSON-like structures
4//! with extensive compile-time type safety and configuration options.
5//!
6//! ## Features
7//!
8//! - Automatic struct generation from JSON-like syntax
9//! - Flexible type inference
10//! - Serde integration
11//! - Compile-time type checking
12//! - Multiple configuration flags
13//!
14//! ## Basic Usage
15//!
16//! ```rust
17//! // Simple struct generation
18//! json2struct!(User {
19//! "first_name" => "John",
20//! "last_name" => "Doe",
21//! "age" => 30
22//! });
23//! ```
24//!
25//! ### Output
26//! ```rust
27//! #[derive(Clone, Deserialize, Serialize)]
28//! struct User {
29//! #[serde(alias = "first_name")]
30//! first_name: String,
31//!
32//! #[serde(alias = "last_name")]
33//! last_name: String,
34//!
35//! #[serde(alias = "age")]
36//! age: f64
37//! }
38//! ```
39//!
40//! ## Example with Flags
41//!
42//! ```rust
43//! // Complex struct with multiple configurations
44//! json2struct!(Company @debug @camel @derive(PartialEq) @store_json {
45//! "company_name" => "Acme Corp",
46//! "employees" => [
47//! {
48//! "id" => 1,
49//! "details" => {
50//! "email" => "john@example.com",
51//! "department" => "Engineering"
52//! }
53//! }
54//! ]
55//! });
56//! ```
57//!
58//! ### Output
59//!
60//! ```rust
61//!
62//!
63//! static COMPANY_JSON_VALUE: LazyLock<Value> = LazyLock::new(||
64//!
65//! {
66//! ::serde_json::from_str(
67//! "{\"company_name\":\"Acme Corp\",\"employees\":[{\"details\":{\"department\":\"Engineering\",\"email\":\"john@example.
68//! com\"},\"id\":1.0}]}",
69//! )
70//! .expect("Couldn't convert the text into valid json")
71//! });
72//!
73//! #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
74//! #[serde(rename_all = "camelCase")]
75//! struct Company {
76//! #[serde(alias = "company_name")]
77//! company_name: String,
78//!
79//! #[serde(alias = "last_name")]
80//! employees: CompanyEmplyees
81//! }
82//!
83//! #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
84//! #[serde(rename_all = "camelCase")]
85//! struct CompanyEmplyees {
86//! #[serde(alias = "id")]
87//! id: f64,
88//!
89//! #[serde(alias = "details")]
90//! details:
91//! }
92//!
93//! #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
94//! #[serde(rename_all = "camelCase")]
95//! struct CompanyEmplyeesDetails {
96//! #[serde(alias = "email")]
97//! email: String,
98//!
99//! #[serde(alias = "department")]
100//! department: String
101//! }
102//!
103//! ```
104//!
105//! ## Supported Flags
106//!
107//! | Flag | Description | Example |
108//! |-----------------|-----------------------------------------------|-------------------------------|
109//! | `@debug` | Adds Debug derive | `@debug` |
110//! | `@snake` | Renames fields to snake_case | `@snake` |
111//! | `@camel` | Renames fields to camelCase | `@camel` |
112//! | `@pascal` | Renames fields to pascal | `@pascal` |
113//! | `@derive(Type)` | Adds custom derives | `@derive(PartialEq, Clone)` |
114//! | `@store_json` | Generates a static JSON Value constant | `@store_json` |
115//!
116
117extern crate proc_macro;
118
119mod generator;
120mod parser;
121
122use proc_macro::TokenStream;
123use quote::{format_ident, quote};
124use syn::parse_macro_input;
125
126/// json2struct: Generates Rust structs from JSON-like structures
127///
128/// # Macro Syntax
129///
130/// ```rust
131/// json2struct!(StructName [flags] {
132/// "key" => value,
133/// ...
134/// })
135/// ```
136///
137/// # Supported Value Types
138/// - Strings: `"value"`
139/// - Numbers: `42`, `3.14`
140/// - Booleans: `true`, `false`
141/// - Null: `null`
142/// - Objects: `{ ... }`
143/// - Arrays: `[ ... ]`
144///
145/// # Examples
146///
147/// Basic Struct:
148/// ```rust
149/// json2struct!(User {
150/// "name" => "John",
151/// "age" => 30
152/// });
153/// ```
154///
155/// Nested Struct:
156/// ```rust
157/// json2struct!(Company @debug {
158/// "name" => "Acme",
159/// "address" => {
160/// "street" => "123 Main St",
161/// "city" => "Anytown"
162/// }
163/// });
164/// ```
165///
166/// # Performance
167/// - Zero-cost abstraction
168/// - Compile-time struct generation
169/// - No runtime overhead
170///
171/// # Errors
172/// Compilation will fail if:
173/// - Invalid JSON structure
174/// - Unsupported types
175/// - Conflicting flags
176#[proc_macro]
177pub fn json2struct(input: TokenStream) -> TokenStream {
178 // Parse the input into our custom macro input structure
179 let json_struct = parse_macro_input!(input as parser::JsonMacroInput);
180
181 // Initialize output token stream
182 let mut output = proc_macro2::TokenStream::new();
183
184 // Optionally generate a static JSON value constant
185 if json_struct.flags.store_json_value {
186 // Convert entries to serde_json::Value
187 let serde_value = serde_json::Value::Object(
188 json_struct
189 .content
190 .entries
191 .clone()
192 .into_iter()
193 .map(|(k, v)| (k, v.to_serde_value()))
194 .collect(),
195 );
196
197 // Convert to string for lazy initialization
198 let serde_value_str = serde_json::to_string(&serde_value).unwrap_or_default();
199
200 // Generate a constant name based on struct name
201 let const_json_ident = format_ident!(
202 "{}_{}",
203 json_struct.struct_name.to_string().to_uppercase(),
204 "JSON_VALUE"
205 );
206
207 // Generate lazy-loaded static JSON value
208 output.extend(quote! {
209 static #const_json_ident: ::std::sync::LazyLock<::serde_json::Value> =
210 ::std::sync::LazyLock::new(||
211 ::serde_json::from_str(#serde_value_str)
212 .expect("Couldn't convert the text into valid json")
213 );
214 });
215 }
216
217 // Generate the main struct and any nested structs
218 let (main_struct, all_structs) =
219 generator::generate_structs(&json_struct, &json_struct.struct_name);
220
221 // Combine all generated code
222 output.extend(quote! {
223 #main_struct
224 #(#all_structs)*
225 });
226
227 // Convert to TokenStream for the compiler
228 output.into()
229}