sonic_rs/value/
macros.rs

1// The file is copied from `serde_json` and modified.
2
3/// Construct a `sonic_rs::Value` from a JSON literal.
4///
5/// ```
6/// # use sonic_rs::json;
7/// #
8/// let value = json!({
9///     "code": 200,
10///     "success": true,
11///     "payload": {
12///         "features": [
13///             "serde",
14///             "json"
15///         ],
16///         "homepage": null
17///     }
18/// });
19/// ```
20///
21/// Variables or expressions can be interpolated into the JSON literal. Any type
22/// interpolated into an array element or object value must implement Serde's
23/// `Serialize` trait, while any type interpolated into a object key must
24/// implement `AsRef<str>`. If the `Serialize` implementation of the
25/// interpolated type decides to fail, or if the interpolated type contains a
26/// map with non-string keys, the `json!` macro will panic.
27///
28/// ```
29/// # use sonic_rs::json;
30/// #
31/// let code = 200;
32/// let features = vec!["sonic_rs", "json"];
33///
34/// let value = json!({
35///     "code": code,
36///     "success": code == 200,
37///     "payload": {
38///         "features": features,
39///         features[0]: features[1]
40///     }
41/// });
42/// assert_eq!(value["code"], 200);
43/// assert_eq!(value["payload"]["features"][0], "sonic_rs");
44/// ```
45///
46/// Trailing commas are allowed inside both arrays and objects.
47///
48/// ```
49/// # use sonic_rs::json;
50/// #
51///
52/// let value = json!(["notice", "the", "trailing", "comma -->",]);
53/// ```
54#[macro_export(local_inner_macros)]
55macro_rules! json {
56    //////////////////////////////////////////////////////////////////////////
57    // The implementation of a static node. It will not create a shared allocator.
58    //
59    // Must be invoked as: json_internal!($($json)+)
60    //////////////////////////////////////////////////////////////////////////
61    (true) => {
62        $crate::Value::new_bool(true)
63    };
64
65    (false) => {
66        $crate::Value::new_bool(false)
67    };
68
69    (null) => {
70        $crate::Value::new_null()
71    };
72
73    ([]) => {
74        $crate::Array::new().into_value()
75    };
76
77    ({}) => {
78        $crate::Object::new().into_value()
79    };
80
81    // Hide distracting implementation details from the generated rustdoc.
82    ($($json:tt)+) => {
83        json_internal!($($json)+)
84    };
85}
86
87/// Construct a `sonic_rs::value::Array` from a JSON array literal.
88///
89/// ```
90/// use sonic_rs::array;
91/// use sonic_rs::json;
92/// use sonic_rs::JsonValueTrait; // tait for `is_null()`
93///
94/// let local = "foo";
95/// let array = array![null, local, true, false, 123,  "hello", 1 == 2, array![1, 2, 3], {"key": "value"}];
96/// assert!(array[0].is_null());
97/// assert_eq!(array[1].as_str(), Some("foo"));
98/// assert_eq!(array[array.len() - 2][0].as_u64(), Some(1));
99/// assert_eq!(array[array.len() - 1], json!({"key": "value"}));
100/// ```
101#[macro_export(local_inner_macros)]
102macro_rules! array {
103    () => {
104        $crate::value::Array::new()
105    };
106
107    ($($tt:tt)+) => {
108        {
109            let value = json_internal!([$($tt)+]);
110            value.into_array().expect("the literal is not a json array")
111        }
112    };
113}
114
115/// Construct a `sonic_rs::value::Object` from a JSON object literal.
116///
117/// ```
118/// # use sonic_rs::object;
119/// #
120/// let code = 200;
121/// let features = vec!["sonic_rs", "json"];
122///
123/// let object = object! {
124///     "code": code,
125///     "success": code == 200,
126///     "payload": {
127///         "features": features,
128///         features[0]: features[1]
129///     }
130/// };
131/// assert_eq!(object["code"], 200);
132/// assert_eq!(object["payload"]["features"][0], "sonic_rs");
133/// ```
134#[macro_export(local_inner_macros)]
135macro_rules! object {
136    () => {
137        $crate::value::Object::new()
138    };
139
140    ($($tt:tt)+) => {
141        {
142            let value = json_internal!({$($tt)+});
143            value.into_object().expect("the literal is not a json object")
144        }
145    };
146}
147
148#[macro_export(local_inner_macros)]
149#[doc(hidden)]
150macro_rules! json_internal {
151    //////////////////////////////////////////////////////////////////////////
152    // TT muncher for parsing the inside of an array [...]. Produces a vec![...]
153    // of the elements.
154    //
155    // Must be invoked as: json_internal!(@array [] $($tt)*)
156    //////////////////////////////////////////////////////////////////////////
157
158    // Done with trailing comma.
159    (@array [$($elems:expr,)*]) => {
160        json_internal_array![$($elems)*]
161    };
162
163    // Done without trailing comma.
164    (@array [$($elems:expr),*]) => {
165        json_internal_array![$($elems)*]
166    };
167
168    // Next element is `null`.
169    (@array [$($elems:expr,)*] null $($rest:tt)*) => {
170        json_internal!(@array  [$($elems,)* json_internal!(null)] $($rest)*)
171    };
172
173    // Next element is `true`.
174    (@array [$($elems:expr,)*] true $($rest:tt)*) => {
175        json_internal!(@array [$($elems,)* json_internal!(true)] $($rest)*)
176    };
177
178    // Next element is `false`.
179    (@array [$($elems:expr,)*] false $($rest:tt)*) => {
180        json_internal!(@array [$($elems,)* json_internal!(false)] $($rest)*)
181    };
182
183    // Next element is an array.
184    (@array [$($elems:expr,)*] [$($array:tt)*] $($rest:tt)*) => {
185        json_internal!(@array [$($elems,)* json_internal!([$($array)*])] $($rest)*)
186    };
187
188    // Next element is a map.
189    (@array [$($elems:expr,)*] {$($map:tt)*} $($rest:tt)*) => {
190        json_internal!(@array  [$($elems,)* json_internal!({$($map)*})] $($rest)*)
191    };
192
193    // Next element is an expression followed by comma.
194    (@array [$($elems:expr,)*] $next:expr, $($rest:tt)*) => {
195        json_internal!(@array [$($elems,)* json_internal!($next),] $($rest)*)
196    };
197
198    // Last element is an expression with no trailing comma.
199    (@array [$($elems:expr,)*] $last:expr) => {
200        json_internal!(@array [$($elems,)* json_internal!($last)])
201    };
202
203    // Comma after the most recent element.
204    (@array [$($elems:expr),*] , $($rest:tt)*) => {
205        json_internal!(@array [$($elems,)*] $($rest)*)
206    };
207
208    // Unexpected token after most recent element.
209    (@array [$($elems:expr),*] $unexpected:tt $($rest:tt)*) => {
210        json_unexpected!($unexpected)
211    };
212
213    //////////////////////////////////////////////////////////////////////////
214    // TT muncher for parsing the inside of an object {...}. Each entry is
215    // inserted into the given map variable.
216    //
217    // Must be invoked as: json_internal!(@object $map () ($($tt)*) ($($tt)*))
218    //
219    // We require two copies of the input tokens so that we can match on one
220    // copy and trigger errors on the other copy.
221    //////////////////////////////////////////////////////////////////////////
222
223    // Done.
224    (@object $object:ident () () ()) => {};
225
226    // Insert the current entry followed by trailing comma.
227    (@object $object:ident [$($key:tt)+] ($value:expr) , $($rest:tt)*) => {
228        let key: &str = ($($key)+).as_ref();
229        let _ = $object.insert(key, $value);
230        json_internal!(@object $object () ($($rest)*) ($($rest)*));
231    };
232
233    // Current entry followed by unexpected token.
234    (@object $object:ident [$($key:tt)+] ($value:expr) $unexpected:tt $($rest:tt)*) => {
235        json_unexpected!($unexpected);
236    };
237
238    // Insert the last entry without trailing comma.
239    (@object $object:ident [$($key:tt)+] ($value:expr)) => {
240        let key: &str = ($($key)+).as_ref();
241        let _ = $object.insert(key, $value);
242    };
243
244    // Next value is `null`.
245    (@object $object:ident ($($key:tt)+) (: null $($rest:tt)*) $copy:tt) => {
246        json_internal!(@object $object [$($key)+] (json_internal!(null)) $($rest)*);
247    };
248
249    // Next value is `true`.
250    (@object $object:ident ($($key:tt)+) (: true $($rest:tt)*) $copy:tt) => {
251        json_internal!(@object $object [$($key)+] (json_internal!(true)) $($rest)*);
252    };
253
254    // Next value is `false`.
255    (@object $object:ident ($($key:tt)+) (: false $($rest:tt)*) $copy:tt) => {
256        json_internal!(@object $object [$($key)+] (json_internal!(false)) $($rest)*);
257    };
258
259    // Next value is an array.
260    (@object $object:ident ($($key:tt)+) (: [$($array:tt)*] $($rest:tt)*) $copy:tt) => {
261        json_internal!(@object $object [$($key)+] (json_internal!([$($array)*])) $($rest)*);
262    };
263
264    // Next value is a map.
265    (@object $object:ident ($($key:tt)+) (: {$($map:tt)*} $($rest:tt)*) $copy:tt) => {
266        json_internal!(@object $object [$($key)+] (json_internal!({$($map)*})) $($rest)*);
267    };
268
269    // Next value is an expression followed by comma.
270    (@object $object:ident ($($key:tt)+) (: $value:expr , $($rest:tt)*) $copy:tt) => {
271        json_internal!(@object $object [$($key)+] (json_internal!($value)) , $($rest)*);
272    };
273
274    // Last value is an expression with no trailing comma.
275    (@object $object:ident ($($key:tt)+) (: $value:expr) $copy:tt) => {
276        json_internal!(@object $object [$($key)+] (json_internal!($value)));
277    };
278
279    // Missing value for last entry. Trigger a reasonable error message.
280    (@object $object:ident ($($key:tt)+) (:) $copy:tt) => {
281        // "unexpected end of macro invocation"
282        json_internal!();
283    };
284
285    // Missing colon and value for last entry. Trigger a reasonable error
286    // message.
287    (@object $object:ident ($($key:tt)+) () $copy:tt) => {
288        // "unexpected end of macro invocation"
289        json_internal!();
290    };
291
292    // Misplaced colon. Trigger a reasonable error message.
293    (@object $object:ident () (: $($rest:tt)*) ($colon:tt $($copy:tt)*)) => {
294        // Takes no arguments so "no rules expected the token `:`".
295        json_unexpected!($colon);
296    };
297
298    // Found a comma inside a key. Trigger a reasonable error message.
299    (@object $object:ident ($($key:tt)*) (, $($rest:tt)*) ($comma:tt $($copy:tt)*)) => {
300        // Takes no arguments so "no rules expected the token `,`".
301        json_unexpected!($comma);
302    };
303
304    // Key is fully parenthesized. This avoids clippy double_parens false
305    // positives because the parenthesization may be necessary here.
306    (@object $object:ident () (($key:expr) : $($rest:tt)*) $copy:tt) => {
307        json_internal!(@object $object ($key) (: $($rest)*) (: $($rest)*));
308    };
309
310    // Refuse to absorb colon token into key expression.
311    (@object $object:ident ($($key:tt)*) (: $($unexpected:tt)+) $copy:tt) => {
312        json_expect_expr_comma!($($unexpected)+);
313    };
314
315    // Munch a token into the current key.
316    (@object $object:ident ($($key:tt)*) ($tt:tt $($rest:tt)*) $copy:tt) => {
317        json_internal!(@object $object ($($key)* $tt) ($($rest)*) ($($rest)*));
318    };
319
320    //////////////////////////////////////////////////////////////////////////
321    // The main implementation.
322    //
323    // Must be invoked as: json_internal!($($json)+)
324    //////////////////////////////////////////////////////////////////////////
325
326    (true) => {
327        $crate::Value::new_bool(true)
328    };
329
330    (false) => {
331        $crate::Value::new_bool(false)
332    };
333
334    (null) => {
335        $crate::Value::new_null()
336    };
337
338    ([]) => {
339        $crate::Value::new_array()
340    };
341
342    ([ $($tt:tt)+ ]) => {
343        json_internal!(@array [] $($tt)+)
344    };
345
346    ({}) => {
347        $crate::Value::new_object()
348    };
349
350    ({ $($tt:tt)+ }) => {
351        {
352            let mut obj_value = $crate::Value::new_object_with(8);
353            json_internal!(@object obj_value () ($($tt)+) ($($tt)+));
354            obj_value
355        }
356    };
357
358    // Any Serialize type: numbers, strings, struct literals, variables etc.
359    // Must be below every other rule.
360    ($other:expr) => {
361        $crate::value::to_value(&$other).unwrap()
362    };
363}
364
365// The json_internal macro above cannot invoke vec directly because it uses
366// local_inner_macros. A vec invocation there would resolve to $crate::vec.
367// Instead invoke vec here outside of local_inner_macros.
368#[macro_export(local_inner_macros)]
369#[doc(hidden)]
370macro_rules! json_internal_array {
371    ($($content:tt)*) => {
372        {
373            let mut arr_value = $crate::Value::new_array_with(8);
374            $(
375                arr_value.append_value($content);
376            )*
377            arr_value
378        }
379    };
380}
381
382#[macro_export]
383#[doc(hidden)]
384macro_rules! json_unexpected {
385    () => {};
386}
387
388#[macro_export]
389#[doc(hidden)]
390macro_rules! json_expect_expr_comma {
391    ($e:expr , $($tt:tt)*) => {};
392}
393
394#[cfg(test)]
395mod test {
396    use std::collections::HashMap;
397
398    use crate::value::value_trait::JsonValueTrait;
399
400    #[test]
401    fn test_json_macro() {
402        assert!(json!(true).is_true());
403        assert!(json!(false).is_false());
404        assert!(json!(null).is_null());
405        assert!(json!("123").is_str());
406        assert!(json!(vec![1]).is_array());
407        assert_eq!(json!(vec![1, 2, 3][2]).as_i64(), Some(3));
408
409        let buf = json!([1, 2, 3]);
410        let arr = json!([true, false, null, 1, 2, 3, "hi", 1 == 2, buf[1] == buf[2]]);
411        assert!(arr.is_array());
412        assert!(arr[arr.len() - 1].is_false());
413
414        let key = "i";
415        let key2 = "\"i\"";
416        let obj = json!({
417            "a": true,
418            "b": false,
419            "c": null,
420            "array": vec![1, 2, 3],
421            "map": ({
422                let mut map = HashMap::<String, String>::new();
423                map.insert("a".to_string(), "b".to_string());
424                map
425            }),
426            "f": 2.333,
427            "g": "hi",
428            "h": 1 == 2,
429            key: {
430                key2: [buf[1] == buf[2], 1],
431            },
432        });
433        assert!(obj.is_object());
434        assert!(obj["a"].is_true());
435        assert!(obj["array"][0].as_u64().unwrap() == 1);
436        assert!(obj["map"]["a"].as_str().unwrap() == "b");
437        assert!(obj[key][key2][1].as_u64().unwrap() == 1);
438
439        let obj = json!({
440            "a": { "b" : {"c": [[[]], {}, {}]} }
441        });
442        assert!(obj["a"]["b"]["c"][0][0].is_array());
443    }
444
445    #[test]
446    fn test_array_macro() {
447        let arr = array![true, false, null, 1, 2, 3, "hi", 1 == 2];
448        assert!(arr[arr.len() - 1].is_false());
449
450        let buf = array![1, 2, 3];
451        let arr = array![true, false, null, 1, 2, 3, "hi", 1 == 2, buf[1] == buf[2]];
452        assert!(arr[arr.len() - 1].is_false());
453    }
454
455    #[test]
456    fn test_object_macro() {
457        let obj = object! {
458            "a": true,
459            "b": false,
460            "c": null,
461            "d": 1,
462            "e": 2,
463            "f": 3,
464            "g": "hi",
465            "h": 1 == 2,
466        };
467        assert!(obj["a"].is_true());
468
469        let arr = array![1, 2, 3];
470        let obj = object! {
471            "a": true,
472            "b": false,
473            "c": null,
474            "d": 1,
475            "e": 2,
476            "f": 3,
477            "g": "hi",
478            "h": 1 == 2,
479            "i": {
480                "i": [arr[1] == arr[2], 1],
481            },
482        };
483        assert!(obj["i"]["i"][1].as_u64().unwrap() == 1);
484    }
485}