diff --git a/firmware/src/config.rs b/firmware/src/config.rs index d8959da..378f659 100644 --- a/firmware/src/config.rs +++ b/firmware/src/config.rs @@ -1,4 +1,4 @@ -use crate::json::{self, FromJson}; +use crate::json::{self, FromJson, FromJsonObject}; use alloc::string::String; use core::fmt; use core::ops::Deref; @@ -12,11 +12,11 @@ use log::{debug, info, warn}; #[derive(Default)] pub struct SensitiveString(String); -impl TryFrom for SensitiveString { - type Error = json::TryFromValueError; - - fn try_from(value: json::Value) -> Result { - Ok(Self(value.try_into()?)) +impl FromJson for SensitiveString { + async fn from_json( + reader: &mut json::Reader, + ) -> Result> { + Ok(Self(reader.read().await?)) } } @@ -67,22 +67,18 @@ pub struct Config { pub wifi_password: SensitiveString, } -impl FromJson for Config { - async fn from_json( +impl FromJsonObject for Config { + async fn read_next( + &mut self, + key: String, reader: &mut json::Reader, - ) -> Result> { - let mut this = Self::default(); - reader - .read_object(|k, v: json::Value| { - match &*k { - "wifi-ssid" => this.wifi_ssid = v.try_into()?, - "wifi-password" => this.wifi_password = v.try_into()?, - _ => (), - } - Ok(()) - }) - .await?; - Ok(this) + ) -> Result<(), json::Error> { + match &*key { + "wifi-ssid" => self.wifi_ssid = reader.read().await?, + "wifi-password" => self.wifi_password = reader.read().await?, + _ => _ = reader.read_any().await?, + } + Ok(()) } } diff --git a/firmware/src/json/mod.rs b/firmware/src/json/mod.rs index 8e0610f..ac12be6 100644 --- a/firmware/src/json/mod.rs +++ b/firmware/src/json/mod.rs @@ -4,7 +4,7 @@ mod error; pub use self::error::Error; mod reader; -pub use self::reader::{FromJson, Reader}; +pub use self::reader::{FromJson, FromJsonArray, FromJsonObject, Reader}; mod value; pub use self::value::{TryFromValueError, Value}; diff --git a/firmware/src/json/reader.rs b/firmware/src/json/reader.rs index 53b764c..70563a6 100644 --- a/firmware/src/json/reader.rs +++ b/firmware/src/json/reader.rs @@ -1,6 +1,7 @@ use super::error::Error; use super::value::Value; use alloc::boxed::Box; +use alloc::collections::BTreeMap; use alloc::string::String; use alloc::vec::Vec; use core::iter::Extend; @@ -65,13 +66,12 @@ impl Reader { } /// Read and parse JSON object - /// A JSON object is read and parsed key by key. The given closure is called for every key - /// value pair as it is parsed. This doesn't need to allocate memory for all keys and values of - /// the object, just for one key value pair at a time. - pub async fn read_object( - &mut self, - mut f: impl FnMut(String, T) -> Result<(), Error>, - ) -> Result<(), Error> { + /// A JSON object is read and parsed field by field. The given type is created and is called + /// to read each field's value. This doesn't allocate any memory while reading the object + /// (except for the current key), so the type's implementation can choose how values are + /// stored. + pub async fn read_object(&mut self) -> Result> { + let mut obj = T::default(); self.expect(b'{').await?; loop { self.trim().await?; @@ -79,14 +79,13 @@ impl Reader { self.trim().await?; self.expect(b':').await?; self.trim().await?; - let value = self.read().await?; - f(key, value)?; + obj.read_next(key, self).await?; self.trim().await?; match self.peek().await? { b',' => self.consume(), b'}' => { self.consume(); - break Ok(()); + break Ok(obj); } ch => break Err(Error::unexpected(ch)), } @@ -94,24 +93,21 @@ impl Reader { } /// Read and parse JSON array - /// A JSON array is read and parsed element by element. The given closure is called for every - /// element as it is parsed. This doesn't need to allocate memory for all elements of the - /// array, just for one element at a time. - pub async fn read_array( - &mut self, - mut f: impl FnMut(T) -> Result<(), Error>, - ) -> Result<(), Error> { + /// A JSON array is read and parsed element by element. The given type is created and is + /// called to read each element. This doesn't allocate any memory while reading the array, + /// so the type's implementation can choose how elements are stored. + pub async fn read_array(&mut self) -> Result> { + let mut vec = T::default(); self.expect(b'[').await?; loop { self.trim().await?; - let elem = self.read().await?; - f(elem)?; + vec.read_next(self).await?; self.trim().await?; match self.peek().await? { b',' => self.consume(), b']' => { self.consume(); - break Ok(()); + break Ok(vec); } ch => break Err(Error::unexpected(ch)), } @@ -365,29 +361,16 @@ impl FromJson for String { } } -impl> FromJson for T { - async fn from_json(reader: &mut Reader) -> Result> { - let mut vec = Self::default(); - reader - .read_array(|elem| { - vec.extend([elem]); - Ok(()) - }) - .await?; - Ok(vec) +// FIXME: Unfortunately, a generic `T: FromJsonArray` would be a conflicting implementation +impl FromJson for Vec { + async fn from_json(reader: &mut Reader) -> Result, Error> { + reader.read_array().await } } -impl FromJson for Vec<(String, Value)> { - async fn from_json(reader: &mut Reader) -> Result> { - let mut vec = Self::default(); - reader - .read_object(|k, v| { - vec.extend([(k, v)]); - Ok(()) - }) - .await?; - Ok(vec) +impl FromJson for T { + async fn from_json(reader: &mut Reader) -> Result> { + reader.read_object().await } } @@ -397,6 +380,52 @@ impl FromJson for Value { } } +/// Deserialize from streaming JSON array +/// The given method is called for every element and gets a reader that MUST be used to read the +/// next element. +pub trait FromJsonArray: Sized + Default { + /// Read next array element from given JSON reader + async fn read_next( + &mut self, + reader: &mut Reader, + ) -> Result<(), Error>; +} + +impl FromJsonArray for Vec { + async fn read_next( + &mut self, + reader: &mut Reader, + ) -> Result<(), Error> { + let elem = reader.read().await?; + self.push(elem); + Ok(()) + } +} + +/// Deserialize from streaming JSON object +/// The given method is called for every field and gets a reader that MUST be used to read the +/// next value. +pub trait FromJsonObject: Sized + Default { + /// Read next object value from given JSON reader + async fn read_next( + &mut self, + key: String, + reader: &mut Reader, + ) -> Result<(), Error>; +} + +impl FromJsonObject for BTreeMap { + async fn read_next( + &mut self, + key: String, + reader: &mut Reader, + ) -> Result<(), Error> { + let value = reader.read().await?; + self.insert(key, value); + Ok(()) + } +} + #[cfg(test)] mod tests { use super::*; @@ -421,23 +450,19 @@ mod tests { baz: bool, } - impl FromJson for Test { - async fn from_json( + impl FromJsonObject for Test { + async fn read_next( + &mut self, + key: String, reader: &mut Reader, - ) -> Result> { - let mut test = Self::default(); - reader - .read_object(|k, v: Value| { - match &*k { - "foo" => test.foo = v.try_into()?, - "bar" => test.bar = v.try_into()?, - "baz" => test.baz = v.try_into()?, - _ => (), - } - Ok(()) - }) - .await?; - Ok(test) + ) -> Result<(), Error> { + match &*key { + "foo" => self.foo = reader.read().await?, + "bar" => self.bar = reader.read().await?, + "baz" => self.baz = reader.read().await?, + _ => _ = reader.read_any().await?, + } + Ok(()) } } @@ -473,47 +498,31 @@ mod tests { assert_read_eq!( r#"{"foo": "hi", "bar": 42, "baz": true}"#, read_any, - Ok(Value::Object(vec![ + Ok(Value::Object(BTreeMap::from([ ("foo".into(), Value::String("hi".into())), ("bar".into(), Value::Integer(42)), ("baz".into(), Value::Boolean(true)), - ])) + ]))) ); assert_read_eq!("buzz", read_any, Err(Error::Unexpected('b'))); } #[async_std::test] async fn read_object() { - let json = r#"{"foo": "hi", "bar": 42, "baz": true}"#; - let mut values = Vec::new(); - let collect = |k, v: Value| { - values.push((k, v)); - Ok(()) - }; - assert_eq!(reader(json).read_object(collect).await, Ok(())); - assert_eq!(values.len(), 3); - assert_eq!(values[0].0, "foo"); - assert_eq!(values[0].1, Value::String("hi".into())); - assert_eq!(values[1].0, "bar"); - assert_eq!(values[1].1, Value::Integer(42)); - assert_eq!(values[2].0, "baz"); - assert_eq!(values[2].1, Value::Boolean(true)); + assert_read_eq!( + r#"{"foo": "hi", "bar": 42, "baz": true}"#, + read_object, + Ok(BTreeMap::from([ + ("foo".to_string(), Value::String("hi".into())), + ("bar".to_string(), Value::Integer(42)), + ("baz".to_string(), Value::Boolean(true)), + ])) + ); } #[async_std::test] async fn read_array() { - let json = "[1, 2, 3, 4]"; - let mut values = Vec::new(); - let collect = |v: Value| { - values.push(v); - Ok(()) - }; - assert_eq!(reader(json).read_array(collect).await, Ok(())); - assert_eq!(values.len(), 4); - assert_eq!(values[0], Value::Integer(1)); - assert_eq!(values[1], Value::Integer(2)); - assert_eq!(values[2], Value::Integer(3)); - assert_eq!(values[3], Value::Integer(4)); + assert_read_eq!("[1, 2, 3, 4]", read_array, Ok(vec![1, 2, 3, 4])); } #[async_std::test] diff --git a/firmware/src/json/value.rs b/firmware/src/json/value.rs index 27221e2..066be30 100644 --- a/firmware/src/json/value.rs +++ b/firmware/src/json/value.rs @@ -1,3 +1,4 @@ +use alloc::collections::BTreeMap; use alloc::string::{String, ToString}; use alloc::vec::Vec; use core::str::FromStr; @@ -40,7 +41,7 @@ pub enum Value { Decimal(f64), String(String), Array(Vec), - Object(Vec<(String, Value)>), + Object(BTreeMap), } impl From<()> for Value { @@ -157,14 +158,14 @@ impl From> for Value { } } -impl From<&[(String, Value)]> for Value { - fn from(value: &[(String, Value)]) -> Self { +impl From<[(String, Value); N]> for Value { + fn from(value: [(String, Value); N]) -> Self { Self::Object(value.into()) } } -impl From> for Value { - fn from(value: Vec<(String, Value)>) -> Self { +impl From> for Value { + fn from(value: BTreeMap) -> Self { Self::Object(value) } } @@ -365,7 +366,7 @@ impl TryFrom for Vec { } } -impl TryFrom for Vec<(String, Value)> { +impl TryFrom for BTreeMap { type Error = TryFromValueError; fn try_from(value: Value) -> Result { diff --git a/firmware/src/json/writer.rs b/firmware/src/json/writer.rs index 793b1c8..25b9a11 100644 --- a/firmware/src/json/writer.rs +++ b/firmware/src/json/writer.rs @@ -1,6 +1,7 @@ use super::error::Error; use super::value::Value; use alloc::boxed::Box; +use alloc::collections::BTreeMap; use alloc::string::{String, ToString}; use alloc::vec::Vec; use embedded_io_async::Write; @@ -161,9 +162,9 @@ impl<'w, W: Write> ObjectWriter<'w, W> { /// Write object fields from iterable collections pub async fn fields_from<'a, K, V, I>(&mut self, iter: I) -> Result<&mut Self, Error> where - K: AsRef + 'a, + K: AsRef + ?Sized + 'a, V: ToJson + 'a, - I: IntoIterator, + I: IntoIterator, { for (key, value) in iter { self.field(key.as_ref(), value).await?; @@ -298,43 +299,19 @@ impl ToJson for Vec { } } -impl ToJson for [(&str, T)] { +impl, V: ToJson> ToJson for [(K, V)] { async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { writer .write_object() .await? - .fields_from(self) + .fields_from(self.iter().map(|(k, v)| (k.as_ref(), v))) .await? .finish() .await } } -impl ToJson for [(String, T)] { - async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { - writer - .write_object() - .await? - .fields_from(self) - .await? - .finish() - .await - } -} - -impl ToJson for Vec<(&str, T)> { - async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { - writer - .write_object() - .await? - .fields_from(self) - .await? - .finish() - .await - } -} - -impl ToJson for Vec<(String, T)> { +impl, V: ToJson> ToJson for BTreeMap { async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { writer .write_object() @@ -447,19 +424,20 @@ mod tests { ); assert_write_eq!( write_any, - &Value::Object(vec![ + &Value::Object(BTreeMap::from([ ("foo".into(), Value::String("hi".into())), ("bar".into(), Value::Integer(42)), ("baz".into(), Value::Boolean(true)), - ]), - Ok(r#"{"foo": "hi", "bar": 42, "baz": true}"#) + ])), + // Value's inner BTreeMap reorders fields + Ok(r#"{"bar": 42, "baz": true, "foo": "hi"}"#) ); } #[async_std::test] async fn write_object() { let mut writer = writer(); - let res = (&mut writer) + let res = writer .write_object() .await .unwrap()