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}