Skip to main content

edikt_core/
macros.rs

1//! The `json!` value-construction macro.
2//!
3//! Parity with `serde_json` / `jsonc_parser`, whose `json!` is the shape every
4//! Rust user already has in their fingers (jhheider/edikt#67). It builds a
5//! [`Value`](crate::Value), not a document: the CST is what round-trips bytes,
6//! and this is the data model an edit *writes into* it.
7//!
8//! Deliberately not a re-parse of a JSON string literal - the macro expands to
9//! direct constructor calls, so a malformed literal is a compile error rather
10//! than a runtime one, and interpolated expressions never go through a
11//! serialize/parse round trip.
12
13/// Construct a [`Value`](crate::Value) from JSON-ish literal syntax.
14///
15/// ```
16/// use edikt_core::{Value, json};
17///
18/// let v = json!({
19///     "name": "edikt",
20///     "version": 1,
21///     "lossless": true,
22///     "formats": ["jsonc", "toml", "yaml"],
23///     "parent": null,
24/// });
25/// assert_eq!(v.to_json(), r#"{"name":"edikt","version":1,"lossless":true,"formats":["jsonc","toml","yaml"],"parent":null}"#);
26/// ```
27///
28/// Keys may be literals or parenthesised expressions, and any value position
29/// accepts an expression that converts into a `Value`:
30///
31/// ```
32/// use edikt_core::{Value, json};
33///
34/// let key = "dynamic";
35/// let count = 3_i64;
36/// let v = json!({ (key): count, "doubled": count * 2 });
37/// assert_eq!(v.to_json(), r#"{"dynamic":3,"doubled":6}"#);
38/// ```
39///
40/// Nesting, arrays, and trailing commas all work:
41///
42/// ```
43/// use edikt_core::{Value, json};
44///
45/// let v = json!([1, [2, 3], { "k": [true, null] },]);
46/// assert_eq!(v.to_json(), r#"[1,[2,3],{"k":[true,null]}]"#);
47/// ```
48#[macro_export]
49macro_rules! json {
50    (null) => { $crate::Value::Null };
51
52    ([]) => { $crate::Value::Array(::std::vec::Vec::new()) };
53    ([ $($elem:tt)+ ]) => {
54        $crate::Value::Array($crate::json_array![[] $($elem)+])
55    };
56
57    ({}) => { $crate::Value::Object(::std::vec::Vec::new()) };
58    ({ $($member:tt)+ }) => {
59        $crate::Value::Object($crate::json_object![[] $($member)+])
60    };
61
62    // Any other single token tree is an ordinary expression. `Value` itself
63    // converts through the identity `From` impl, so `json!(existing)` nests.
64    ($other:expr) => { $crate::Value::from($other) };
65}
66
67/// Accumulate array elements. Internal to [`json!`]; not a stable API.
68#[macro_export]
69#[doc(hidden)]
70macro_rules! json_array {
71    // Done: no more input.
72    ([$($done:expr),*]) => { ::std::vec![$($done),*] };
73    ([$($done:expr),*] ,) => { ::std::vec![$($done),*] };
74
75    // A nested array or object element must be matched before the general
76    // `expr` arm, because `[..]` / `{..}` are not expressions here.
77    ([$($done:expr),*] [$($arr:tt)*] , $($rest:tt)*) => {
78        $crate::json_array![[$($done,)* $crate::json!([$($arr)*])] $($rest)*]
79    };
80    ([$($done:expr),*] [$($arr:tt)*]) => {
81        $crate::json_array![[$($done,)* $crate::json!([$($arr)*])]]
82    };
83    ([$($done:expr),*] {$($obj:tt)*} , $($rest:tt)*) => {
84        $crate::json_array![[$($done,)* $crate::json!({$($obj)*})] $($rest)*]
85    };
86    ([$($done:expr),*] {$($obj:tt)*}) => {
87        $crate::json_array![[$($done,)* $crate::json!({$($obj)*})]]
88    };
89
90    // `null` ahead of the `expr` arms: once captured as an `expr` fragment it
91    // can no longer re-match the keyword arm in `json!`, and would resolve as a
92    // path to a value named `null`. (serde_json treats it as a keyword too.)
93    ([$($done:expr),*] null , $($rest:tt)*) => {
94        $crate::json_array![[$($done,)* $crate::Value::Null] $($rest)*]
95    };
96    ([$($done:expr),*] null) => {
97        $crate::json_array![[$($done,)* $crate::Value::Null]]
98    };
99
100    ([$($done:expr),*] $next:expr , $($rest:tt)*) => {
101        $crate::json_array![[$($done,)* $crate::json!($next)] $($rest)*]
102    };
103    ([$($done:expr),*] $last:expr) => {
104        $crate::json_array![[$($done,)* $crate::json!($last)]]
105    };
106}
107
108/// Accumulate object members. Internal to [`json!`]; not a stable API.
109#[macro_export]
110#[doc(hidden)]
111macro_rules! json_object {
112    ([$($done:expr),*]) => { ::std::vec![$($done),*] };
113    ([$($done:expr),*] ,) => { ::std::vec![$($done),*] };
114
115    // Nested composite values, ahead of the general `expr` arm (see above).
116    ([$($done:expr),*] $key:tt : [$($arr:tt)*] , $($rest:tt)*) => {
117        $crate::json_object![[$($done,)* ($crate::json_key!($key), $crate::json!([$($arr)*]))] $($rest)*]
118    };
119    ([$($done:expr),*] $key:tt : [$($arr:tt)*]) => {
120        $crate::json_object![[$($done,)* ($crate::json_key!($key), $crate::json!([$($arr)*]))]]
121    };
122    ([$($done:expr),*] $key:tt : {$($obj:tt)*} , $($rest:tt)*) => {
123        $crate::json_object![[$($done,)* ($crate::json_key!($key), $crate::json!({$($obj)*}))] $($rest)*]
124    };
125    ([$($done:expr),*] $key:tt : {$($obj:tt)*}) => {
126        $crate::json_object![[$($done,)* ($crate::json_key!($key), $crate::json!({$($obj)*}))]]
127    };
128
129    // See the `null` note in `json_array!`.
130    ([$($done:expr),*] $key:tt : null , $($rest:tt)*) => {
131        $crate::json_object![[$($done,)* ($crate::json_key!($key), $crate::Value::Null)] $($rest)*]
132    };
133    ([$($done:expr),*] $key:tt : null) => {
134        $crate::json_object![[$($done,)* ($crate::json_key!($key), $crate::Value::Null)]]
135    };
136
137    ([$($done:expr),*] $key:tt : $val:expr , $($rest:tt)*) => {
138        $crate::json_object![[$($done,)* ($crate::json_key!($key), $crate::json!($val))] $($rest)*]
139    };
140    ([$($done:expr),*] $key:tt : $val:expr) => {
141        $crate::json_object![[$($done,)* ($crate::json_key!($key), $crate::json!($val))]]
142    };
143}
144
145/// Render an object key. Internal to [`json!`]; not a stable API.
146///
147/// A parenthesised key is an expression (`(key): v`); anything else is taken
148/// for its literal text, so bare and quoted keys both work.
149#[macro_export]
150#[doc(hidden)]
151macro_rules! json_key {
152    (($key:expr)) => {
153        ::std::string::ToString::to_string(&$key)
154    };
155    ($key:literal) => {
156        ::std::string::ToString::to_string(&$key)
157    };
158    ($key:tt) => {
159        ::std::stringify!($key).to_string()
160    };
161}
162
163#[cfg(test)]
164mod tests {
165    use crate::Value;
166
167    #[test]
168    fn builds_scalars_and_empties() {
169        assert_eq!(json!(null), Value::Null);
170        assert_eq!(json!(true), Value::Bool(true));
171        assert_eq!(json!(42), Value::Int(42));
172        assert_eq!(json!(2.5), Value::Float(2.5));
173        assert_eq!(json!("s"), Value::Str("s".into()));
174        assert_eq!(json!([]).to_json(), "[]");
175        assert_eq!(json!({}).to_json(), "{}");
176    }
177
178    #[test]
179    fn nests_arrays_and_objects_and_tolerates_trailing_commas() {
180        let v = json!({
181            "a": [1, 2, [3, {"deep": true}]],
182            "b": { "c": { "d": null } },
183        });
184        assert_eq!(
185            v.to_json(),
186            r#"{"a":[1,2,[3,{"deep":true}]],"b":{"c":{"d":null}}}"#
187        );
188        assert_eq!(json!([1, 2, 3,]).to_json(), "[1,2,3]");
189    }
190
191    #[test]
192    fn object_keys_preserve_insertion_order() {
193        // Order is user-visible in every format edikt emits, so the macro must
194        // not reorder; a HashMap-backed builder would.
195        let v = json!({ "z": 1, "a": 2, "m": 3 });
196        assert_eq!(v.to_json(), r#"{"z":1,"a":2,"m":3}"#);
197    }
198
199    #[test]
200    fn interpolates_expressions_in_key_and_value_position() {
201        let key = "dynamic";
202        let n = 3_i64;
203        let owned = String::from("owned");
204        let v = json!({ (key): n * 2, "s": owned, "computed": n > 1 });
205        assert_eq!(v.to_json(), r#"{"dynamic":6,"s":"owned","computed":true}"#);
206    }
207
208    #[test]
209    fn nests_an_existing_value_through_the_identity_conversion() {
210        let inner = json!({ "already": "built" });
211        let outer = json!({ "wrapped": inner });
212        assert_eq!(outer.to_json(), r#"{"wrapped":{"already":"built"}}"#);
213    }
214
215    #[test]
216    fn converts_options_vecs_and_slices() {
217        let some: Option<i64> = Some(7);
218        let none: Option<i64> = None;
219        let xs: Vec<i64> = vec![1, 2];
220        let slice: &[&str] = &["a", "b"];
221        let v = json!({ "some": some, "none": none, "xs": xs, "slice": slice });
222        assert_eq!(
223            v.to_json(),
224            r#"{"some":7,"none":null,"xs":[1,2],"slice":["a","b"]}"#
225        );
226    }
227
228    #[test]
229    fn collects_pairs_into_an_object() {
230        let v: Value = [("a", 1_i64), ("b", 2)].into_iter().collect();
231        assert_eq!(v.to_json(), r#"{"a":1,"b":2}"#);
232    }
233
234    #[test]
235    fn integer_widths_all_convert() {
236        assert_eq!(json!(1_i8), Value::Int(1));
237        assert_eq!(json!(1_u32), Value::Int(1));
238        assert_eq!(json!(-1_isize), Value::Int(-1));
239        assert_eq!(json!(1.5_f32), Value::Float(1.5));
240    }
241}