json_proc_macro/
lib.rs

1#![cfg_attr(lints_enabled, feature(proc_macro_diagnostic), feature(negative_impls))]
2
3use proc_macro::TokenStream;
4use proc_macro2::{Span, TokenStream as TokenStream2};
5use quote::{quote, ToTokens};
6use syn::{
7    braced, bracketed, parse,
8    parse::{Parse, ParseStream},
9    parse_macro_input,
10    spanned::Spanned,
11    token, Error as SynError, Expr, Ident, Index, ItemEnum, ItemStruct, LitBool, LitInt, LitStr,
12    Member, Result as SynResult, Token,
13};
14
15#[cfg(lints_enabled)]
16// These only work on nightly because they are unstable
17use proc_macro::{Diagnostic, Level};
18
19mod util {
20    use std::iter::Iterator;
21
22    #[must_use]
23    #[inline(always)]
24    // This exists the way it does for two reasons:
25    // 1. utility
26    // 2. proc macros are expanded during build
27    //    so as long as this isn't done at runtime
28    //    it's more or less OK
29    pub fn iter_len<T, I: Iterator<Item = T> + Clone>(iter: &I) -> usize {
30        iter.clone().count()
31    }
32}
33
34enum JsonValue {
35    Object(JsonObject),
36    Array(JsonArray),
37    String(LitStr),
38    Bool(bool),
39    Expr(Expr),
40    Null,
41}
42
43#[derive(Clone)]
44enum JsonKey {
45    Expr(Expr),
46    Lit(String),
47}
48
49impl PartialEq for JsonKey {
50    fn eq(&self, other: &Self) -> bool {
51        match (self, other) {
52            (Self::Lit(s1), Self::Lit(s2)) => *s1 == *s2,
53            (Self::Expr(e1), Self::Expr(e2)) => quote!(#e1).to_string() == quote!(#e2).to_string(),
54            _ => false,
55        }
56    }
57}
58
59struct JsonKeyValue {
60    key: JsonKey,
61    value: JsonValue,
62    key_span: Span,
63}
64
65struct JsonObject {
66    pairs: Vec<JsonKeyValue>,
67}
68
69struct JsonArray {
70    elements: Vec<JsonValue>,
71}
72
73impl Parse for JsonKeyValue {
74    fn parse(input: ParseStream) -> SynResult<Self> {
75        let (key, span) = if input.peek(LitStr) {
76            let item = input.parse::<LitStr>()?;
77            (JsonKey::Lit(item.value()), item.span())
78        } else if cfg!(feature = "exprs-as-keys") {
79            let item = input.parse::<Expr>()?;
80            (JsonKey::Expr(item.clone()), item.span())
81        } else {
82            let item = input.parse::<Ident>()?;
83            (JsonKey::Lit(item.to_string()), item.span())
84        };
85        input.parse::<Token![:]>()?;
86
87        Ok(JsonKeyValue {
88            key,
89            value: input.parse()?,
90            key_span: span,
91        })
92    }
93}
94
95impl Parse for JsonObject {
96    fn parse(input: ParseStream) -> SynResult<Self> {
97        let content;
98        let _ = braced!(content in input);
99        let mut pairs = Vec::new();
100
101        while !content.is_empty() {
102            let pair = content.parse()?;
103            pairs.push(pair);
104            let _ = content.parse::<Token![,]>();
105        }
106
107        {
108            let mut map: Vec<(JsonKey, Span)> = Vec::new();
109            for JsonKeyValue { key, key_span, .. } in &pairs {
110                #[cfg(lints_enabled)]
111                if let Some((key, span2)) = map.iter().find(|(key2, _)| *key2 == *key) {
112                    Diagnostic::spanned(
113                        key_span.unwrap(),
114                        Level::Error,
115                        format!(
116                            "duplicate key {} in object",
117                            match key {
118                                JsonKey::Lit(str) => str.clone(),
119                                JsonKey::Expr(expr) => format!("expression `{}`", quote!(#expr)),
120                            }
121                        ),
122                    )
123                    .help("remove this repeated key")
124                    .span_note(span2.unwrap(), "key first defined here")
125                    .emit();
126                    continue;
127                }
128                map.push((key.clone(), *key_span));
129            }
130        }
131
132        Ok(JsonObject { pairs })
133    }
134}
135
136impl Parse for JsonArray {
137    fn parse(input: ParseStream) -> SynResult<Self> {
138        let content;
139        let _brackets = bracketed!(content in input);
140        let mut elements = Vec::new();
141
142        while !content.is_empty() {
143            let elem = content.parse()?;
144            elements.push(elem);
145            if content.peek(Token![,]) {
146                content.parse::<Token![,]>()?;
147            }
148        }
149
150        Ok(JsonArray {
151            elements,
152            // span: input.span(),
153        })
154    }
155}
156
157/* // This is never used
158impl JsonValue {
159    fn span(&self) -> Option<Span> {
160        match self {
161            JsonValue::Object(json_object) => Some(json_object.span.clone()),
162            JsonValue::Array(json_array) => Some(json_array.span),
163            JsonValue::String(lit_str) => Some(lit_str.span()),
164            JsonValue::Number(expr) => Some(expr.span()),
165            JsonValue::Bool(_) => None,
166            JsonValue::Expr(expr) => Some(expr.span())
167        }
168    }
169}
170*/
171
172fn check_int_overflow(int: LitInt) {
173    let upper = const { 2i64.pow(53) };
174    let lower = -upper;
175    let (clamped, fits) = int.base10_parse::<i64>().map_or((0, false), |v| {
176        (v.clamp(lower, upper), (lower..upper).contains(&v))
177    });
178    if !fits {
179        let mut d = Diagnostic::spanned(
180            int.span().unwrap(),
181            Level::Note,
182            "value may be outside safe integer range of most programming languages",
183        )
184        .note("keeping this value may result in a loss of precision in other languages")
185        .note("-2^53 to 2^53 is the JavaScript safe integer range");
186        if clamped != 0 {
187            d = d.help(format!("change this value or show it clamped: {clamped}"))
188        }
189        d.emit();
190    }
191}
192
193impl Parse for JsonValue {
194    fn parse(input: ParseStream) -> SynResult<Self> {
195        if input.peek(LitStr) {
196            Ok(JsonValue::String(input.parse()?))
197        } else if input.peek(LitBool) {
198            Ok(JsonValue::Bool(input.parse::<LitBool>()?.value))
199        } else if input.peek(token::Brace) {
200            Ok(JsonValue::Object(input.parse()?))
201        } else if input.peek(token::Bracket) {
202            Ok(JsonValue::Array(input.parse()?))
203        } else if input
204            .fork()
205            .parse::<Ident>()
206            .map(|v| v.to_string())
207            .is_ok_and(|v| v == "undefined" || v == "null")
208        {
209            input.parse::<Ident>()?;
210            Ok(JsonValue::Null)
211        } else {
212            #[cfg(lints_enabled)]
213            if let Ok(int) = input.fork().parse::<LitInt>() {
214                check_int_overflow(int)
215            }
216            Ok(JsonValue::Expr(input.parse()?))
217        }
218    }
219}
220
221impl ToTokens for JsonValue {
222    fn to_tokens(&self, tokens: &mut TokenStream2) {
223        match self {
224            JsonValue::Object(obj) => obj.to_tokens(tokens),
225            JsonValue::Array(arr) => arr.to_tokens(tokens),
226            JsonValue::String(litstr) => quote!(format!("\"{}\"", #litstr)).to_tokens(tokens),
227            JsonValue::Bool(b) => (*b).to_tokens(tokens),
228            JsonValue::Expr(expr) => {
229                quote!((::json_proc::ToJson::to_json_string(&(#expr)))).to_tokens(tokens);
230            }
231            JsonValue::Null => quote!("null").to_tokens(tokens),
232        }
233    }
234}
235
236impl ToTokens for JsonKey {
237    fn to_tokens(&self, tokens: &mut TokenStream2) {
238        match self {
239            Self::Lit(str) => quote!(#str).to_tokens(tokens),
240            Self::Expr(expr) => expr.to_tokens(tokens),
241        }
242    }
243}
244
245// Implementing quote for JsonObject to generate valid Rust code
246impl ToTokens for JsonObject {
247    fn to_tokens(&self, tokens: &mut TokenStream2) {
248        if self.pairs.is_empty() {
249            return quote!("{}".to_string()).to_tokens(tokens);
250        }
251        let pairs = &self.pairs;
252        let mut pairs_tokens = Vec::new();
253        for pair in pairs {
254            let key = &pair.key;
255            let value = &pair.value;
256            pairs_tokens.push(quote!(format!("\"{}\":{}", #key, #value)));
257        }
258        let output = quote! {{
259            // format!("{{{}}}", (vec![#(#pairs_tokens),*] as Vec<String>).join(","))
260            let mut string = String::with_capacity(2);
261            string.push('{');
262            #(
263                string.push_str(&#pairs_tokens);
264                string.push(',');
265            )*
266            let _ = string.pop();
267            string.push('}');
268            string
269        }};
270        output.to_tokens(tokens);
271    }
272}
273
274impl ToTokens for JsonArray {
275    fn to_tokens(&self, tokens: &mut TokenStream2) {
276        if self.elements.is_empty() {
277            return quote!("[]").to_tokens(tokens);
278        }
279        let elements = &self.elements;
280        let mut elements_tokens = Vec::new();
281        for elem in elements {
282            elements_tokens.push(quote!(#elem));
283        }
284        let output = quote! {{
285            // format!("[{}]", (vec![#(#elements_tokens),*] as Vec<String>).join(","))
286            let mut string = String::with_capacity(2);
287            string.push('[');
288            #(
289                string.push_str(&#elements_tokens);
290                string.push(',');
291            )*
292            let _ = string.pop();
293            string.push(']');
294            string
295        }};
296        output.to_tokens(tokens);
297    }
298}
299
300// Lints only happen on nightly since diagnostics are
301// unstable features, so change the docs to reflect that.
302#[cfg_attr(lints_enabled, doc = "Lints and properly ")]
303#[cfg_attr(not(lints_enabled), doc = "Properly ")]
304/// formats a JSON object, array, or value.
305///
306/// This macro supports:
307/// - all literals (integers, floats, [`&str`][strlit], [`char`])
308/// - any expression that evaluates to a [`impl ToJson`][ToJson]
309///
310/// If you are looking for custom serialization traits, macros,
311/// and functions, use `serde_json` and `serde` instead.
312///
313#[cfg_attr(
314    not(lints_enabled),
315    doc = "Keep in mind, lints are only enabled on the Nightly channel of Rust."
316)]
317#[cfg_attr(
318    lints_enabled,
319    doc = "Lints are currently enabled because you are using the Nightly channel of Rust."
320)]
321/// ## Examples:
322///
323/// Serializing an object:
324/// ```no_run
325/// // You have to have the `ToJson` trait restriction since
326/// // the json! macro uses ToJson. Should a struct not
327/// // implement ToJson, you can use the derive macro.
328/// fn obj<J: json_proc::ToJson>(input: J) -> String {
329///     json!({
330///         "hello": "world!",
331///         thisDidntNeedQuotes: "wow!",
332///         // this will essentially become `format!("{}", input.to_json_string())`
333///         anExpression: input
334///     })
335/// }
336/// ```
337///
338/// Serializing an array:
339/// ```no_run
340/// fn arr<J: json_proc::ToJson>(input: J) -> String {
341///     json!([
342///         input,
343///         (2 + 11) as f32 / 2.0,
344///         "literal"
345///     ])
346/// }
347/// ```
348///
349/// [strlit]: str
350/// [ToJson]: https://docs.rs/json_proc/latest/json_proc/trait.ToJson.html
351#[proc_macro]
352pub fn json(input: TokenStream) -> TokenStream {
353    let json_value = parse_macro_input!(input as JsonValue);
354
355    quote!(#json_value).into()
356}
357
358/// Derive the ToJson trait for a struct or enum.
359///
360/// ## Example:
361///
362/// ```no_run
363/// # extern crate json_proc;
364/// use json_proc::{ToJson, json};
365///
366/// #[derive(ToJson)]
367/// struct Example1 {
368///     field1: bool,
369///     field2: i8
370/// }
371///
372/// # #[test]
373/// fn print() {
374///     println!("{}", json!(Example1 {
375///         field1: true,
376///         field2: -2
377///     }))
378/// }
379/// ```
380#[proc_macro_derive(ToJson)]
381// TODO: add enum support
382pub fn json_derive(item: TokenStream) -> TokenStream {
383    if let Ok(mut input) = parse::<ItemStruct>(item.clone()) {
384        let ident = &input.ident;
385        let mut members = input.fields.members().peekable();
386        input.generics.make_where_clause();
387        let (impl_generics, ty_generics, _) = input.generics.split_for_impl();
388        let mut where_clause = input.generics.where_clause.clone().unwrap();
389        let type_generics = input.generics.type_params().map(|v| v.ident.clone());
390        let path = quote!(::json_proc::ToJson).into();
391        let path = parse_macro_input!(path as syn::Path);
392        for ty in type_generics {
393            let mut bounds = syn::punctuated::Punctuated::new();
394            bounds.push(syn::TypeParamBound::Trait(syn::TraitBound { paren_token: None, modifier: syn::TraitBoundModifier::None, lifetimes: None, path: path.clone() }));
395            where_clause.predicates.push(syn::WherePredicate::Type(syn::PredicateType { lifetimes: None, bounded_ty: syn::Type::Verbatim(quote!(#ty)), colon_token: Token![:](Span::call_site()), bounds }))
396        }
397
398        let fn_impl = if members
399            .peek()
400            .is_some_and(|v| matches!(v, Member::Unnamed(_)))
401        {
402            if util::iter_len(&members) == 1 {
403                // Generate an impl that uses the first (and only) element in the tuple.
404                quote!(::json_proc::ToJson::to_json_string(&self.0))
405            } else {
406                // Generate an array-like impl.
407                quote! {{
408                    // format!("[{}]", (vec![#(#elements_tokens),*] as Vec<String>).join(","))
409                    let mut string = String::with_capacity(2);
410                    string.push('[');
411                    #(
412                        string.push_str(&(::json_proc::ToJson::to_json_string(&self.#members)));
413                        string.push(',');
414                    )*
415                    let _ = string.pop();
416                    string.push(']');
417                    string
418                }}
419            }
420        } else if members.peek().is_some() {
421            // Generate an object-like impl.
422            quote! {{
423                // format!("{{{}}}", (vec![#(#pairs_tokens),*] as Vec<String>).join(","))
424                let mut string = String::with_capacity(2);
425                string.push('{');
426                #(
427                    string.push('"');
428                    string.push_str(stringify!(#members));
429                    string.push('"');
430                    string.push(':');
431                    string.push_str(&(::json_proc::ToJson::to_json_string(&self.#members)));
432                    string.push(',');
433                )*
434                let _ = string.pop();
435                string.push('}');
436                string
437            }}
438        } else {
439            quote!(stringify!(#ident).to_string())
440        };
441        quote! {
442            impl #impl_generics ToJson for #ident #ty_generics #where_clause {
443                fn to_json_string(&self) -> String {
444                    #fn_impl
445                }
446            }
447        }
448        .into()
449    } else if let Ok(mut input) = parse::<ItemEnum>(item.clone()) {
450        let ident = input.ident;
451        let variants = input.variants.iter().peekable();
452        input.generics.make_where_clause();
453        let (impl_generics, ty_generics, _) = input.generics.split_for_impl();
454        let mut where_clause = input.generics.where_clause.clone().unwrap();
455        let type_generics = input.generics.type_params().map(|v| v.ident.clone());
456        let path = quote!(::json_proc::ToJson).into();
457        let path = parse_macro_input!(path as syn::Path);
458        for ty in type_generics {
459            let mut bounds = syn::punctuated::Punctuated::new();
460            bounds.push(syn::TypeParamBound::Trait(syn::TraitBound { paren_token: None, modifier: syn::TraitBoundModifier::None, lifetimes: None, path: path.clone() }));
461            where_clause.predicates.push(syn::WherePredicate::Type(syn::PredicateType { lifetimes: None, bounded_ty: syn::Type::Verbatim(quote!(#ty)), colon_token: Token![:](Span::call_site()), bounds }))
462        }
463
464        let mut streams: Vec<TokenStream2> = Vec::new();
465        for var in variants {
466            // Handle like a struct.
467            let varident = &var.ident;
468            let mut members = var.fields.members().peekable();
469            let iter_len = util::iter_len(&members);
470            let this_impl = if iter_len == 0 {
471                quote!(Self::#varident => stringify!(#ident).to_string())
472            } else if members
473                .peek()
474                .is_some_and(|v| matches!(v, Member::Unnamed(_)))
475            {
476                if iter_len == 1 {
477                    // Generate an impl that uses the first (and only) element in the tuple.
478                    quote!(Self::#varident(a) => ::json_proc::ToJson::to_json_string(a))
479                } else {
480                    // Generate an array-like impl.
481                    let members = members.map(|v| match v {
482                        Member::Unnamed(i) => {
483                            Ident::new(format!("arg{}", i.index).as_str(), i.span)
484                        }
485                        Member::Named(_) => unreachable!(),
486                    });
487                    let members2 = members.clone();
488                    quote!(Self::#varident( #(#members2),* ) => {
489                        // format!("[{}]", (vec![#(#elements_tokens),*] as Vec<String>).join(","))
490                        let mut string = String::with_capacity(2);
491                        string.push('[');
492                        #(
493                            string.push_str(&(::json_proc::ToJson::to_json_string(#members)));
494                            string.push(',');
495                        )*
496                        let _ = string.pop();
497                        string.push(']');
498                        string
499                    })
500                }
501            } else {
502                // Generate an object-like impl.
503                let members2 = members.clone();
504                quote!(Self::#varident { #(#members2),* } => {
505                    // format!("{{{}}}", (vec![#(#pairs_tokens),*] as Vec<String>).join(","))
506                    let mut string = String::with_capacity(2);
507                    string.push('{');
508                    #(
509                        string.push('"');
510                        string.push_str(stringify!(#members));
511                        string.push('"');
512                        string.push(':');
513                        string.push_str(&(::json_proc::ToJson::to_json_string(#members)));
514                        string.push(',');
515                    )*
516                    let _ = string.pop();
517                    string.push('}');
518                    string
519                })
520            };
521
522            streams.push(this_impl)
523        }
524
525        quote! {
526            impl #impl_generics ToJson for #ident #ty_generics #where_clause {
527                fn to_json_string(&self) -> String {
528                    match self {
529                        #(#streams),*
530                    }
531                }
532            }
533        }
534        .into()
535    } else {
536        SynError::new(
537            TokenStream2::from(item).span(),
538            "expected struct or enum for deriving ToJson",
539        )
540        .into_compile_error()
541        .into()
542    }
543}
544
545#[doc(hidden)]
546#[proc_macro]
547/// Private macro for generating impls of ToJson for tuples.
548pub fn tuple_impl(_: TokenStream) -> TokenStream {
549    const LETTERS: [char; 12] = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L'];
550    let mut streams = Vec::with_capacity(12);
551    for i in 0..12 {
552        let letters = &LETTERS[..(i + 1)]
553            .iter()
554            .map(|ch| Ident::new(&ch.to_string(), Span::call_site()))
555            .collect::<Vec<Ident>>();
556        let nums = (0..(i + 1)).map(Index::from).collect::<Vec<Index>>();
557        let doc_attr = if i == 0 {
558            quote!(#[doc = "`ToJson` is implemented for tuples up to size 12."])
559        } else {
560            quote!(#[doc(hidden)])
561        };
562        streams.push(quote! {
563            #doc_attr
564            impl<#(#letters),*> crate::ToJson for (#(#letters,)*)
565            where
566                #(
567                    #letters: crate::ToJson
568                ),*
569            {
570                fn to_json_string(&self) -> String {
571                    let mut string = String::with_capacity(3);
572                    string.push('[');
573                    #(
574                        string.push_str(&(crate::ToJson::to_json_string(&self.#nums)));
575                        string.push(',');
576                    )*
577                    let _ = string.pop();
578                    string.push(']');
579                    string
580                }
581            }
582        });
583    }
584    quote!(#(#streams)*).into()
585}