Skip to main content

json_streaming/nonblocking/
object.rs

1use crate::shared::*;
2use crate::nonblocking::array::JsonArray;
3use crate::nonblocking::io::NonBlockingWrite;
4use crate::nonblocking::json_writer::JsonWriter;
5
6/// A [JsonObject] is the API for writing a JSON object, i.e. a sequence of key/value pairs. The
7///  closing `}` is written when the [JsonObject] instance goes out of scope, or when its `end()`
8///  function is called.
9///
10/// For nested objects or arrays, the function calls return new [JsonObject] or [JsonArray] instances,
11///  respectively. Rust's type system ensures that applications can only interact with the innermost
12///  such instance, and call outer instances only when all nested instances have gone out of scope.
13///
14/// A typical use of the library is to create a [JsonWriter] and then wrap it in a top-level
15///  [JsonObject] instance.
16pub struct JsonObject<'a, 'b, W: NonBlockingWrite, F: JsonFormatter, FF: FloatFormat> {
17    writer: &'a mut JsonWriter<'b, W, F, FF>,
18    is_initial: bool,
19    is_ended: bool,
20}
21impl<'a, 'b, W: NonBlockingWrite, F: JsonFormatter, FF: FloatFormat> JsonObject<'a, 'b, W, F, FF> {
22    /// Create a new [JsonObject] instance. Application code can do this explicitly only initially
23    ///  as a starting point for writing JSON. Nested objects are created by the library.
24    pub async fn new(writer: &'a mut JsonWriter<'b, W, F, FF>) -> Result<Self, W::Error> {
25        writer.write_bytes(b"{").await?;
26        writer.write_format_after_start_nested().await?;
27        Ok(JsonObject {
28            writer,
29            is_initial: true,
30            is_ended: false,
31        })
32    }
33
34    async fn write_key(&mut self, key: &str) -> Result<(), W::Error> {
35        if !self.is_initial {
36            self.writer.write_bytes(b",").await?;
37            self.writer.write_format_after_element().await?;
38        }
39        self.is_initial = false;
40        self.writer.write_format_indent().await?;
41        self.writer.write_escaped_string(key).await?;
42        self.writer.write_bytes(b":").await?;
43        self.writer.write_format_after_key().await
44    }
45
46    /// Write a pre-serialized JSON snippet as a value with a given key.
47    ///
48    /// NB: This method does *not* perform any checks on the value, assuming it to be a valid JSON
49    ///  value.
50    /// NB: This method does not apply formatting rules to the JSON snippet, but writes it 'as-is'.
51    pub async fn unchecked_write_raw_value(&mut self, key: &str, value_json: &str) -> Result<(), W::Error> {
52        self.write_key(key).await?;
53        self.writer.write_bytes(value_json.as_bytes()).await
54    }
55
56    /// Write a key/value pair with element type 'string', escaping the provided string value.
57    pub async fn write_string_value(&mut self, key: &str, value: &str) -> Result<(), W::Error> {
58        self.write_key(key).await?;
59        self.writer.write_escaped_string(value).await
60    }
61
62    /// Write a key/value pair with element type 'bool'
63    pub async fn write_bool_value(&mut self, key: &str, value: bool) -> Result<(), W::Error> {
64        self.write_key(key).await?;
65        self.writer.write_bool(value).await
66    }
67
68    /// Write a key with a null literal as its value
69    pub async fn write_null_value(&mut self, key: &str) -> Result<(), W::Error> {
70        self.write_key(key).await?;
71        self.writer.write_bytes(b"null").await
72    }
73
74    /// Write a key/value pair with an f64 value. If the value is not finite (i.e. infinite or NaN),
75    ///  a null literal is written instead. Different behavior (e.g. leaving out the whole key/value
76    ///  pair for non-finite numbers, representing them in some other way etc.) is the responsibility
77    ///  of application code.
78    pub async fn write_f64_value(&mut self, key: &str, value: f64) -> Result<(), W::Error> {
79        self.write_key(key).await?;
80        self.writer.write_f64(value).await
81    }
82
83    /// Write a key/value pair with an f32 value. If the value is not finite (i.e. infinite or NaN),
84    ///  a null literal is written instead. Different behavior (e.g. leaving out the whole key/value
85    ///  pair for non-finite numbers, representing them in some other way etc.) is the responsibility
86    ///  of application code.
87    pub async fn write_f32_value(&mut self, key: &str, value: f32) -> Result<(), W::Error> {
88        self.write_key(key).await?;
89        self.writer.write_f32(value).await
90    }
91
92    /// Start a nested object under a given key. This function returns a new [JsonObject] instance
93    ///  for writing elements to the nested object. When the returned [JsonObject] goes out of scope
94    ///  (per syntactic scope or an explicit call to `end()`), the nested object is closed, and
95    ///  application code can continue adding elements to the owning `self` object.
96    pub async fn start_object<'c, 'x>(&'x mut self, key: &str) -> Result<JsonObject<'c, 'b, W, F, FF>, W::Error>
97    where 'a: 'c, 'x: 'c
98    {
99        self.write_key(key).await?;
100        JsonObject::new(&mut self.writer).await
101    }
102
103    /// Start a nested array under a given key. This function returns a new [JsonArray] instance
104    ///  for writing elements to the nested object. When the returned [JsonArray] goes out of scope
105    ///  (per syntactic scope or an explicit call to `end()`), the nested array is closed, and
106    ///  application code can continue adding elements to the owning `self` object.
107    pub async fn start_array<'x, 'c>(&'x mut self, key: &str) -> Result<JsonArray<'c, 'b, W, F, FF>, W::Error>
108    where 'a: 'c, 'x: 'c
109    {
110        self.write_key(key).await?;
111        JsonArray::new(self.writer).await
112    }
113
114    /// Explicitly end this object's lifetime and write the closing bracket.
115    pub async fn end(self) -> Result<(), W::Error> {
116        let mut mut_self = self;
117        mut_self._end().await
118    }
119
120    async fn _end(&mut self) -> Result<(), W::Error> {
121        self.writer.write_format_before_end_nested(self.is_initial).await?;
122        self.writer.write_bytes(b"}").await?;
123        self.is_ended = true;
124        Ok(())
125    }
126}
127
128macro_rules! write_obj_int {
129    ($t:ty ; $f:ident) => {
130impl<'a, 'b, W: NonBlockingWrite, F: JsonFormatter, FF: FloatFormat> JsonObject<'a, 'b, W, F, FF> {
131    /// Write a key/value pair with an int value of type $t.
132    pub async fn $f(&mut self, key: &str, value: $t) -> Result<(), W::Error> {
133        self.write_key(key).await?;
134        self.writer.write_raw_num(value).await
135    }
136}
137    };
138}
139write_obj_int!(i8; write_i8_value);
140write_obj_int!(u8; write_u8_value);
141write_obj_int!(i16; write_i16_value);
142write_obj_int!(u16; write_u16_value);
143write_obj_int!(i32; write_i32_value);
144write_obj_int!(u32; write_u32_value);
145write_obj_int!(i64; write_i64_value);
146write_obj_int!(u64; write_u64_value);
147write_obj_int!(i128; write_i128_value);
148write_obj_int!(u128; write_u128_value);
149write_obj_int!(isize; write_isize_value);
150write_obj_int!(usize; write_usize_value);
151
152
153
154#[cfg(test)]
155pub mod tests {
156    use super::*;
157    use crate::nonblocking::array::tests::ArrayCommand;
158    use rstest::rstest;
159    use std::io;
160
161    pub enum ObjectCommand {
162        Null(&'static str),
163        Bool(&'static str, bool),
164        Raw(&'static str, &'static str),
165        String(&'static str, &'static str),
166        U8(&'static str, u8),
167        I8(&'static str, i8),
168        U16(&'static str, u16),
169        I16(&'static str, i16),
170        U32(&'static str, u32),
171        I32(&'static str, i32),
172        U64(&'static str, u64),
173        I64(&'static str, i64),
174        U128(&'static str, u128),
175        I128(&'static str, i128),
176        Usize(&'static str, usize),
177        Isize(&'static str, isize),
178        F64(&'static str, f64),
179        F32(&'static str, f32),
180        Object(&'static str, Vec<ObjectCommand>),
181        Array(&'static str, Vec<ArrayCommand>)
182    }
183    impl ObjectCommand {
184        pub async fn apply(&self, obj: &mut JsonObject<'_, '_, Vec<u8>, CompactFormatter, DefaultFloatFormat>) {
185            match self {
186                ObjectCommand::Null(key) => obj.write_null_value(key).await.unwrap(),
187                ObjectCommand::Bool(key, b) => obj.write_bool_value(key, *b).await.unwrap(),
188                ObjectCommand::Raw(key, b) => obj.unchecked_write_raw_value(key, *b).await.unwrap(),
189                ObjectCommand::String(key, s) => obj.write_string_value(key, s).await.unwrap(),
190                ObjectCommand::U8(key, n) => obj.write_u8_value(key, *n).await.unwrap(),
191                ObjectCommand::I8(key, n) => obj.write_i8_value(key, *n).await.unwrap(),
192                ObjectCommand::U16(key, n) => obj.write_u16_value(key, *n).await.unwrap(),
193                ObjectCommand::I16(key, n) => obj.write_i16_value(key, *n).await.unwrap(),
194                ObjectCommand::U32(key, n) => obj.write_u32_value(key, *n).await.unwrap(),
195                ObjectCommand::I32(key, n) => obj.write_i32_value(key, *n).await.unwrap(),
196                ObjectCommand::U64(key, n) => obj.write_u64_value(key, *n).await.unwrap(),
197                ObjectCommand::I64(key, n) => obj.write_i64_value(key, *n).await.unwrap(),
198                ObjectCommand::U128(key, n) => obj.write_u128_value(key, *n).await.unwrap(),
199                ObjectCommand::I128(key, n) => obj.write_i128_value(key, *n).await.unwrap(),
200                ObjectCommand::Usize(key, n) => obj.write_usize_value(key, *n).await.unwrap(),
201                ObjectCommand::Isize(key, n) => obj.write_isize_value(key, *n).await.unwrap(),
202                ObjectCommand::F64(key, x) => obj.write_f64_value(key, *x).await.unwrap(),
203                ObjectCommand::F32(key, x) => obj.write_f32_value(key, *x).await.unwrap(),
204                ObjectCommand::Object(key, cmds) => {
205                    let mut nested = obj.start_object(key).await.unwrap();
206                    for cmd in cmds {
207                        Box::pin(cmd.apply(&mut nested)).await;
208                    }
209                    nested.end().await.unwrap();
210                }
211                ObjectCommand::Array(key, cmds) => {
212                    let mut nested = obj.start_array(key).await.unwrap();
213                    for cmd in cmds {
214                        Box::pin(cmd.apply(&mut nested)).await;
215                    }
216                    nested.end().await.unwrap();
217                }
218            }
219        }
220    }
221
222    #[rstest]
223    #[case::empty(vec![], "{}")]
224    #[case::single(vec![ObjectCommand::Null("a")], r#"{"a":null}"#)]
225    #[case::two(vec![ObjectCommand::U32("a", 1), ObjectCommand::U32("b", 2)], r#"{"a":1,"b":2}"#)]
226    #[case::nested_arr(vec![ObjectCommand::Array("x", vec![])], r#"{"x":[]}"#)]
227    #[case::nested_arr_with_el(vec![ObjectCommand::Array("y", vec![ArrayCommand::U32(5)])], r#"{"y":[5]}"#)]
228    #[case::nested_arr_first(vec![ObjectCommand::Array("z", vec![]), ObjectCommand::U32("q", 4)], r#"{"z":[],"q":4}"#)]
229    #[case::nested_arr_last(vec![ObjectCommand::U32("q", 6), ObjectCommand::Array("z", vec![])], r#"{"q":6,"z":[]}"#)]
230    #[case::nested_arr_between(vec![ObjectCommand::U32("a", 7), ObjectCommand::Array("b", vec![]), ObjectCommand::U32("c", 9)], r#"{"a":7,"b":[],"c":9}"#)]
231    #[case::two_nested_arrays(vec![ObjectCommand::Array("d", vec![]), ObjectCommand::Array("e", vec![])], r#"{"d":[],"e":[]}"#)]
232    #[case::nested_obj(vec![ObjectCommand::Object("f", vec![])], r#"{"f":{}}"#)]
233    #[case::nested_obj_with_el(vec![ObjectCommand::Object("g", vec![ObjectCommand::U32("a", 3)])], r#"{"g":{"a":3}}"#)]
234    #[case::nested_obj_first(vec![ObjectCommand::Object("h", vec![]), ObjectCommand::U32("i", 0)], r#"{"h":{},"i":0}"#)]
235    #[case::nested_obj_last(vec![ObjectCommand::U32("j", 2), ObjectCommand::Object("k", vec![])], r#"{"j":2,"k":{}}"#)]
236    #[case::two_nested_objects(vec![ObjectCommand::Object("l", vec![]), ObjectCommand::Object("m", vec![])], r#"{"l":{},"m":{}}"#)]
237    #[tokio::test]
238    async fn test_object(#[case] cmds: Vec<ObjectCommand>, #[case] expected: &str) -> io::Result<()> {
239        let mut buf = Vec::new();
240        let mut writer = JsonWriter::new_compact(&mut buf);
241        {
242            let mut object_ser = JsonObject::new(&mut writer).await?;
243            for cmd in cmds {
244                cmd.apply(&mut object_ser).await;
245            }
246            object_ser.end().await?;
247        }
248
249        let actual = String::from_utf8(buf).unwrap();
250        assert_eq!(actual, expected);
251        Ok(())
252    }
253
254    #[rstest]
255    #[case::null(ObjectCommand::Null("a"), "null")]
256    #[case::bool_true(ObjectCommand::Bool("a", true), "true")]
257    #[case::bool_false(ObjectCommand::Bool("a", false), "false")]
258    #[case::raw(ObjectCommand::Raw("a", "x y z"), "x y z")]
259    #[case::string(ObjectCommand::String("a", "asdf"), r#""asdf""#)]
260    #[case::string_escaped(ObjectCommand::String("a", "\r\n"), r#""\r\n""#)]
261    #[case::u8(ObjectCommand::U8("a", 2u8), "2")]
262    #[case::i8(ObjectCommand::I8("a", -3i8), "-3")]
263    #[case::u16(ObjectCommand::U16("a", 4u16), "4")]
264    #[case::i16(ObjectCommand::I16("a", -5i16), "-5")]
265    #[case::u32(ObjectCommand::U32("a", 6u32), "6")]
266    #[case::i32(ObjectCommand::I32("a", -7i32), "-7")]
267    #[case::u64(ObjectCommand::U64("a", 8u64), "8")]
268    #[case::i64(ObjectCommand::I64("a", -9i64), "-9")]
269    #[case::u128(ObjectCommand::U128("a", 12u128), "12")]
270    #[case::i128(ObjectCommand::I128("a", -13i128), "-13")]
271    #[case::usize(ObjectCommand::Usize("a", 10usize), "10")]
272    #[case::isize(ObjectCommand::Isize("a", -11isize), "-11")]
273    #[case::f64(ObjectCommand::F64("a", 2.0), "2")]
274    #[case::f64_exp_5(ObjectCommand::F64("a", 1.234e5), "123400")]
275    #[case::f64_exp_10(ObjectCommand::F64("a", 1.234e10), "1.234e10")]
276    #[case::f64_exp_20(ObjectCommand::F64("a", 1.234e20), "1.234e20")]
277    #[case::f64_exp_neg_3(ObjectCommand::F64("a", 1.234e-3), "0.001234")]
278    #[case::f64_exp_neg_10(ObjectCommand::F64("a", 1.234e-10), "1.234e-10")]
279    #[case::f64_neg(ObjectCommand::F64("a", -2.0), "-2")]
280    #[case::f64_neg_exp_5(ObjectCommand::F64("a", -1.234e5), "-123400")]
281    #[case::f64_neg_exp_10(ObjectCommand::F64("a", -1.234e10), "-1.234e10")]
282    #[case::f64_neg_exp_20(ObjectCommand::F64("a", -1.234e20), "-1.234e20")]
283    #[case::f64_neg_exp_neg_3(ObjectCommand::F64("a", -1.234e-3), "-0.001234")]
284    #[case::f64_neg_exp_neg_10(ObjectCommand::F64("a", -1.234e-10), "-1.234e-10")]
285    #[case::f64_inf(ObjectCommand::F64("a", f64::INFINITY), "null")]
286    #[case::f64_neg_inf(ObjectCommand::F64("a", f64::NEG_INFINITY), "null")]
287    #[case::f64_nan(ObjectCommand::F64("a", f64::NAN), "null")]
288    #[case::f32(ObjectCommand::F32("a", 2.0), "2")]
289    #[case::f32_exp_5(ObjectCommand::F32("a", 1.234e5), "123400")]
290    #[case::f32_exp_10(ObjectCommand::F32("a", 1.234e10), "1.234e10")]
291    #[case::f32_exp_20(ObjectCommand::F32("a", 1.234e20), "1.234e20")]
292    #[case::f32_exp_neg_3(ObjectCommand::F32("a", 1.234e-3), "0.001234")]
293    #[case::f32_exp_neg_10(ObjectCommand::F32("a", 1.234e-10), "1.234e-10")]
294    #[case::f32_neg(ObjectCommand::F32("a", -2.0), "-2")]
295    #[case::f32_neg_exp_5(ObjectCommand::F32("a", -1.234e5), "-123400")]
296    #[case::f32_neg_exp_10(ObjectCommand::F32("a", -1.234e10), "-1.234e10")]
297    #[case::f32_neg_exp_20(ObjectCommand::F32("a", -1.234e20), "-1.234e20")]
298    #[case::f32_neg_exp_neg_3(ObjectCommand::F32("a", -1.234e-3), "-0.001234")]
299    #[case::f32_neg_exp_neg_10(ObjectCommand::F32("a", -1.234e-10), "-1.234e-10")]
300    #[case::f32_inf(ObjectCommand::F32("a", f32::INFINITY), "null")]
301    #[case::f32_neg_inf(ObjectCommand::F32("a", f32::NEG_INFINITY), "null")]
302    #[case::f32_nan(ObjectCommand::F32("a", f32::NAN), "null")]
303    #[tokio::test]
304    async fn test_write_value(#[case] cmd: ObjectCommand, #[case] expected: &str) -> io::Result<()> {
305        {
306            let mut buf = Vec::new();
307            let mut writer = JsonWriter::new_compact(&mut buf);
308            {
309                let mut object_ser = JsonObject::new(&mut writer).await?;
310                cmd.apply(&mut object_ser).await;
311                object_ser.end().await?;
312            }
313
314            let actual = String::from_utf8(buf).unwrap();
315            let expected = format!(r#"{}"a":{}{}"#, "{", expected, "}");
316            assert_eq!(actual, expected);
317        }
318
319        // test with and without preceding element to verify that 'initial' is handled correctly
320        {
321            let mut buf = Vec::new();
322            let mut writer = JsonWriter::new_compact(&mut buf);
323            {
324                let mut object_ser = JsonObject::new(&mut writer).await?;
325                object_ser.write_null_value("x").await?;
326                cmd.apply(&mut object_ser).await;
327                object_ser.write_u32_value("y", 5).await?;
328                object_ser.end().await?;
329            }
330
331            let actual = String::from_utf8(buf).unwrap();
332            let expected = format!(r#"{}"x":null,"a":{},"y":5{}"#, "{", expected, "}");
333            assert_eq!(actual, expected);
334        }
335
336        Ok(())
337    }
338}