json_in_type/
object.rs

1//! Serialization to JSON objects like `{"x":1,"y":null}`
2
3use super::string::JSONString;
4use super::JSONValue;
5use std::collections::HashMap;
6use std::hash::BuildHasher;
7use std::hash::Hash;
8use std::io;
9
10/// Write a single key-value pair
11fn write_object_entry<W, K, V>(w: &mut W, key: &K, value: &V) -> io::Result<()>
12where
13    W: io::Write,
14    K: JSONString,
15    V: JSONValue,
16{
17    key.write_json(w)?;
18    w.write_all(b":")?;
19    value.write_json(w)
20}
21
22/// Write a list of key-value pairs to a writer as a json object
23fn write_object<'a, W, K, V, I>(w: &mut W, iter: &mut I) -> io::Result<()>
24where
25    W: io::Write,
26    K: JSONString,
27    V: JSONValue,
28    V: 'a,
29    K: 'a,
30    I: Iterator<Item = (&'a K, &'a V)>,
31{
32    w.write_all(b"{")?;
33    if let Some((key, value)) = iter.next() {
34        write_object_entry(w, key, value)?;
35        for (key, value) in iter {
36            w.write_all(b",")?;
37            write_object_entry(w, key, value)?;
38        }
39    }
40    w.write_all(b"}")
41}
42
43/// A struct used to wrap another type and make it serializable as a json object.
44/// The other type has to be able to yield (key, value) pairs by implementing IntoIterator.
45///
46/// # Examples
47///
48/// Serialize a vec as a json object
49///
50/// ```
51/// use json_in_type::object::ToJSONObject;
52/// use json_in_type::JSONValue;
53///
54/// let my_obj = ToJSONObject(vec![("x", 1), ("y", 2)]);
55///
56/// assert_eq!("{\"x\":1,\"y\":2}", my_obj.to_json_string());
57/// ```
58pub struct ToJSONObject<K, V, I>(pub I)
59where
60    K: JSONString,
61    V: JSONValue,
62    for<'a> &'a I: IntoIterator<Item = &'a (K, V)>;
63
64impl<K, V, I> JSONValue for ToJSONObject<K, V, I>
65where
66    K: JSONString,
67    V: JSONValue,
68    for<'a> &'a I: IntoIterator<Item = &'a (K, V)>,
69{
70    fn write_json<W: io::Write>(&self, w: &mut W) -> io::Result<()> {
71        let mut iter = (&self.0).into_iter().map(|(k, v)| (k, v)); // Convert a borrowed tuple to a tuple of borrowed values
72        write_object(w, &mut iter)
73    }
74}
75
76/// Serialize a HashMap to a JSON object. The property order is not guaranteed.
77impl<K: JSONString + Eq + Hash, V: JSONValue, S: BuildHasher> JSONValue for HashMap<K, V, S> {
78    fn write_json<W: io::Write>(&self, w: &mut W) -> io::Result<()> {
79        write_object(w, &mut self.iter())
80    }
81}
82
83pub trait JSONObject: JSONValue {
84    fn write_json_ending<W: io::Write>(&self, f: &mut W, first: bool) -> io::Result<()>;
85    #[inline]
86    fn write_json_full<W: io::Write>(&self, w: &mut W) -> io::Result<()> {
87        self.write_json_ending(w, true)
88    }
89}
90
91/// A JSON object stored as a static linked list.
92/// This is a generic structure that specializes at compile-time
93/// to a structure whose type stores the exact shape of the object.
94pub struct JSONObjectEntry<K: JSONString, V: JSONValue, U: JSONObject> {
95    pub key: K,
96    pub value: V,
97    pub next: U,
98}
99
100impl<K: JSONString, V: JSONValue, U: JSONObject> JSONObject for JSONObjectEntry<K, V, U> {
101    #[inline(always)]
102    fn write_json_ending<W: io::Write>(&self, w: &mut W, first: bool) -> io::Result<()> {
103        w.write_all(if first { b"{" } else { b"," })?;
104        self.key.write_json(w)?;
105        w.write_all(b":")?;
106        self.value.write_json(w)?;
107        self.next.write_json_ending(w, false)
108    }
109}
110
111impl<K: JSONString, V: JSONValue, U: JSONObject> JSONValue for JSONObjectEntry<K, V, U> {
112    #[inline(always)]
113    fn write_json<W: io::Write>(&self, w: &mut W) -> io::Result<()> {
114        self.write_json_full(w)
115    }
116}
117
118/// An empty JSON object. This is a Zero Sized Type.
119/// It just serves to mark the end of an object in its type,
120/// but takes no space in memory at runtime.
121pub struct JSONObjectEnd;
122
123impl JSONObject for JSONObjectEnd {
124    #[inline(always)]
125    fn write_json_ending<W: io::Write>(&self, w: &mut W, first: bool) -> io::Result<()> {
126        w.write_all(if first { b"{}" } else { b"}" })
127    }
128}
129
130impl JSONValue for JSONObjectEnd {
131    fn write_json<W: io::Write>(&self, w: &mut W) -> io::Result<()> {
132        self.write_json_full(w)
133    }
134}
135
136#[macro_export]
137#[doc(hidden)]
138macro_rules! inlined_json_object {
139    (key : $key:ident, value : $value:expr, next : $next:expr) => {{
140        use $crate::object::JSONObject;
141        use $crate::JSONValue;
142
143        struct InlinedJSONObjectEntry<V: JSONValue, U: JSONObject> {
144            value: V,
145            next: U,
146        }
147
148        impl<V: JSONValue, U: JSONObject> JSONObject for InlinedJSONObjectEntry<V, U> {
149            #[inline(always)]
150            fn write_json_ending<W: ::std::io::Write>(
151                &self,
152                w: &mut W,
153                first: bool,
154            ) -> ::std::io::Result<()> {
155                w.write_all(
156                    if first {
157                        concat!("{\"", stringify!($key), "\":")
158                    } else {
159                        concat!(",\"", stringify!($key), "\":")
160                    }
161                    .as_bytes(),
162                )?;
163                self.value.write_json(w)?;
164                self.next.write_json_ending(w, false)
165            }
166        }
167
168        impl<V: JSONValue, U: JSONObject> JSONValue for InlinedJSONObjectEntry<V, U> {
169            fn write_json<W: ::std::io::Write>(&self, w: &mut W) -> ::std::io::Result<()> {
170                self.write_json_full(w)
171            }
172        }
173
174        InlinedJSONObjectEntry {
175            value: $value,
176            next: $next,
177        }
178    }};
179}
180
181/// Creates a static json object that can be serialized very fast.
182/// Returns a struct implementing [`JSONValue`](trait.JSONValue.html).
183///
184/// The macro takes a comma-separated list of key-value pairs.
185/// Keys can be written literally, or surrounded by brackets (`[key]`)
186/// to reference external variables. A value can be omitted, in which
187/// case the the key name must be the name of a variable currently in scope,
188/// from which the value will be taken.
189///
190/// Values must be expressions of a type implementing JSONValue.
191///
192/// # Examples
193///
194/// ### Create a simple json object.
195/// ```
196/// use json_in_type::*;
197///
198/// let my_obj = json_object!{
199///     hello: "world"
200/// };
201///
202/// assert_eq!(r#"{"hello":"world"}"#, my_obj.to_json_string());
203/// ```
204///
205/// ### Shorthand property names
206/// It is common to create a json object from a set of variables,
207/// using the variable names as keys and the contents of the variables as values.
208/// ```
209/// use json_in_type::*;
210///
211/// let x = "hello";
212/// let y = 42;
213/// let z = true;
214/// let my_obj = json_object!{ x, y, z };
215///
216/// assert_eq!(r#"{"x":"hello","y":42,"z":true}"#, my_obj.to_json_string());
217/// ```
218///
219/// ### Reference external variables
220/// ```
221/// use json_in_type::*;
222///
223/// let x = "hello";
224/// let my_obj = json_object!{
225///     [x]: "world", // The trailing comma is ok
226/// };
227///
228/// assert_eq!(r#"{"hello":"world"}"#, my_obj.to_json_string());
229/// ```
230///
231/// ### Compute keys dynamically
232/// ```
233/// use json_in_type::*;
234///
235/// let x = "hello";
236/// let my_obj = json_object!{
237///     [x]: "world",
238///     [[x, "_suffix"].concat()]: 42
239/// };
240///
241/// assert_eq!(r#"{"hello":"world","hello_suffix":42}"#, my_obj.to_json_string());
242/// ```
243#[macro_export]
244macro_rules! json_object {
245    () => { $crate::object::JSONObjectEnd{} };
246    // A null value
247    ($key:ident : null, $($rest:tt)*) => { json_object!($key : (), $($rest)*) };
248    ($key:ident : true, $($rest:tt)*) => { json_object!($key : $crate::base_types::JSONtrue, $($rest)*) };
249    ($key:ident : false, $($rest:tt)*) => { json_object!($key : $crate::base_types::JSONfalse, $($rest)*) };
250    // Literal key
251    ($key:ident : $value:expr, $($rest:tt)*) => {
252        inlined_json_object!{
253            key: $key,
254            value: $value,
255            next: json_object!($($rest)*)
256         }
257    };
258    // The key is an expression in square brackets
259    ([$key:expr] : $value:expr, $($rest:tt)*) => {
260        $crate::object::JSONObjectEntry {
261            key: $key,
262            value: $value,
263            next: json_object!($($rest)*)
264        }
265    };
266    // A key that references a variable of the same name
267    ($key:ident, $($rest:tt)*) => { json_object!($key : $key, $($rest)*) };
268    // Simply adding a trailing colon
269    ($key:ident : $value:ident) => { json_object!($key:$value,) };
270    ($key:ident : $value:expr) => { json_object!($key:$value,) };
271    ([$key:expr] : $value:expr) => { json_object!([$key]:$value,) };
272    ($key:ident) => { json_object!($key,) };
273}
274
275#[cfg(test)]
276mod tests {
277    // Note this useful idiom: importing names from outer (for mod tests) scope.
278    use super::*;
279
280    #[test]
281    fn test_empty() {
282        assert_eq!("{}", JSONObjectEnd.to_json_string());
283        assert_eq!("{}", json_object!().to_json_string());
284    }
285
286    #[test]
287    fn test_single_pair() {
288        assert_eq!(
289            r#"{"x":{}}"#,
290            json_object!(x: json_object!()).to_json_string()
291        );
292        // With a trailing comma:
293        assert_eq!(
294            r#"{"x":{}}"#,
295            json_object!(x: json_object!(),).to_json_string()
296        );
297    }
298
299    #[test]
300    fn test_two_pairs() {
301        assert_eq!(
302            r#"{"x":{},"y":{}}"#,
303            json_object! {
304                x : json_object!(),
305                y : json_object!()
306            }
307            .to_json_string()
308        );
309    }
310
311    #[test]
312    fn test_nested() {
313        assert_eq!(
314            r#"{"x":{"y":{}}}"#,
315            json_object! {
316                x : json_object! {
317                    y : json_object!()
318                }
319            }
320            .to_json_string()
321        );
322    }
323
324    #[test]
325    fn test_dynamic_keys() {
326        let x = "x";
327        let y = String::from("y");
328        assert_eq!(
329            r#"{"x":{"y":{}}}"#,
330            json_object! {
331                [x] : json_object! {
332                    [y] : json_object!()
333                }
334            }
335            .to_json_string()
336        );
337    }
338
339    #[test]
340    fn test_hashmap() {
341        let mut map = HashMap::new();
342        map.insert("x", 1);
343        map.insert("y", 2);
344        // The order in which the keys are serialized is not guaranteed
345        let expected = vec![r#"{"x":1,"y":2}"#, r#"{"y":2,"x":1}"#];
346        assert!(expected.contains(&&map.to_json_string()[..]));
347    }
348
349    #[test]
350    fn test_zero_size() {
351        use std::mem::size_of_val;
352        // We should pay only for what we use.
353        // If we have no reference to external values, the resulting object should take 0 bytes
354        // in memory
355        let json_obj = json_object! {
356            null: null,
357            nested: json_object! {
358                also_null: (),
359                bool : true,
360                deeper: json_object! {
361                    other_bool: false
362                }
363            }
364        };
365        assert_eq!(0, size_of_val(&json_obj));
366    }
367}