Deserialising data with type/content?
Hi,
I'm not sure if the following is possible with miniserde, or at least I haven't found a way so far. If I have data like:
{
"tag": "MyTag",
"content": {
"myTypeProp": true
}
}
and
{
"tag": "MyOtherTag",
"content": {
"myOtherTypeProp": true
}
}
where I need to know the value of tag before I can deserialise the data in content. Is there any way to handle that in miniserde? In serde I would use and enum with #[serde(tag = "tag", content = "content")], however I know this is not supported.
I looked at a custom implementation of Deserialize/Visitor and treating as a Map (Like here) but it needed type to always be read before content which is unreliable.
The closest I got was a custom struct which can hold all possible properties for content, e.g:
use miniserde::{make_place, Deserialize, Result};
use miniserde::de::{Deserialize as DeserializeTrait, Map, Visitor};
make_place!(Place);
#[derive(Debug, Deserialize)]
struct FirstInner {
pub first_field: u32
}
#[derive(Debug, Deserialize)]
struct SecondInner {
pub second_field: u32
}
#[derive(Debug, Deserialize)]
struct CombinedInner {
pub first_field: Option<u32>,
pub second_field: Option<u32>
}
#[derive(Debug)]
enum Something {
First(FirstInner),
Second(SecondInner),
Third
}
#[derive(Deserialize)]
enum SomethingInner {
First,
Second,
Third
}
struct SomethingBuilder<'a> {
tag: Option<SomethingInner>,
content: Option<CombinedInner>,
out: &'a mut Option<Something>,
}
impl Visitor for Place<Something> {
fn map(&mut self) -> Result<Box<dyn Map + '_>> {
Ok(Box::new(SomethingBuilder {
tag: None,
content: None,
out: &mut self.out,
}))
}
}
impl<'a> Map for SomethingBuilder<'a> {
fn key(&mut self, k: &str) -> Result<&mut dyn Visitor> {
match k {
"tag" => Ok(Deserialize::begin(&mut self.tag)),
"content" => Ok(Deserialize::begin(&mut self.content)),
_ => Ok(<dyn Visitor>::ignore()),
}
}
fn finish(&mut self) -> Result<()> {
let tag = self.tag.take().ok_or(miniserde::Error)?;
*self.out = match tag {
SomethingInner::First => {
let content = self.content.take().ok_or(miniserde::Error)?;
Some(Something::First(FirstInner { first_field: content.first_field.unwrap() }))
}
SomethingInner::Second => {
let content = self.content.take().ok_or(miniserde::Error)?;
Some(Something::Second(SecondInner { second_field: content.second_field.unwrap() }))
}
SomethingInner::Third => {
Some(Something::Third)
}
};
Ok(())
}
}
impl DeserializeTrait for Something {
fn begin(out: &mut Option<Self>) -> &mut dyn Visitor {
// All Deserialize impls look like this.
Place::new(out)
}
}
#[cfg(test)]
mod test {
use miniserde::json;
use super::*;
#[test]
fn first() {
let str: &str = r#"{
"tag": "First",
"content": {
"first_field": 100
}
}"#;
let _ = json::from_str::<Something>(str).unwrap();
}
#[test]
fn second() {
let str: &str = r#"{
"tag": "Second",
"content": {
"second_field": 100
}
}"#;
let _ = json::from_str::<Something>(str).unwrap();
}
#[test]
fn third() {
let str: &str = r#"{
"tag": "Third"
}"#;
let _ = json::from_str::<Something>(str).unwrap();
}
}
The solution you arrived at looks all right to me, if it's necessary to support "content" appearing before "tag" in the input data.
If it's possible to rely on "tag" always appearing before "content", then I might instead do something like:
struct SomethingBuilder<'a> {
tag: Option<SomethingTag>,
content: Option<SomethingContent>,
out: &'a mut Option<Something>,
}
#[derive(Deserialize)]
enum SomethingTag {
First,
Second,
Third,
}
enum SomethingContent {
First(Option<FirstInner>),
Second(Option<SecondInner>),
}
impl Visitor for Place<Something> {
fn map(&mut self) -> Result<Box<dyn Map + '_>> {
Ok(Box::new(SomethingBuilder {
tag: None,
content: None,
out: &mut self.out,
}))
}
}
impl<'a> Map for SomethingBuilder<'a> {
fn key(&mut self, k: &str) -> Result<&mut dyn Visitor> {
match k {
"tag" => Ok(Deserialize::begin(&mut self.tag)),
"content" => match self.tag {
Some(SomethingTag::First) => {
let SomethingContent::First(content) =
self.content.insert(SomethingContent::First(None))
else {
unreachable!();
};
Ok(Deserialize::begin(content))
}
Some(SomethingTag::Second) => {
let SomethingContent::Second(content) =
self.content.insert(SomethingContent::Second(None))
else {
unreachable!();
};
Ok(Deserialize::begin(content))
}
None | Some(SomethingTag::Third) => Err(miniserde::Error),
},
_ => Ok(<dyn Visitor>::ignore()),
}
}
fn finish(&mut self) -> Result<()> {
*self.out = Some(match self.content.take() {
Some(SomethingContent::First(Some(first))) => Something::First(first),
Some(SomethingContent::Second(Some(second))) => Something::Second(second),
None if matches!(self.tag, Some(SomethingTag::Third)) => Something::Third,
_ => return Err(miniserde::Error),
});
Ok(())
}
}
impl Deserialize for Something {
fn begin(out: &mut Option<Self>) -> &mut dyn Visitor {
Place::new(out)
}
}