Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Flatten an array of partial objects into struct #2069

Closed
m-rots opened this issue Aug 9, 2021 · 2 comments
Closed

Flatten an array of partial objects into struct #2069

m-rots opened this issue Aug 9, 2021 · 2 comments
Labels

Comments

@m-rots
Copy link

m-rots commented Aug 9, 2021

Hello there! First of all, thanks for the amazing project! :)

I have been digging through Serde's documentation on how to handle a specific case, but haven't been able to create a generic version so far. I'd greatly appreciate it if someone could give me some pointers on how to solve this deserialisation issue generically.

The problem

I'm trying to work with an API which formats its JSON responses as such:

[
    {
        "Account": {
            "id": 0,
        }
    },
    {
        "Account": {
            "id": 1,
        }
    },
    {
        "Session": {
            "id": "Hello",
        }
    }
]

Now, the issue is that some objects are guaranteed to only appear once (Session), while others are guaranteed to be a list (Account). With those guarantees in mind, I would like to deserialise the response into the following structs:

use serde::Deserialize;

#[derive(Deserialize)]
#[serde(rename_all = "PascalCase")]
struct Response {
    account: Vec<Account>,
    session: Session,
}

#[derive(Deserialize)]
struct Account {
    id: usize,
}

#[derive(Deserialize)]
struct Session {
    id: String,
}

Handwritten Deserialize

I've managed to write a handwritten Deserialize implementation (Playground, Gist). However, my question to you is whether there is room for improvement. Specifically:

  • Can a version of this code exist where I do not have to specify Field and Temporary?
  • Can a version of this code work generically on either a Wrapper<Response> struct or a deserialize_with function?

Sources used

Thank you!

@Stargateur
Copy link

Here a I believe little better implementation:

use serde::{Deserialize, Deserializer};
use serde_json::json;

#[derive(Debug, Deserialize, PartialEq, Eq)]
struct Account {
    id: usize,
}

#[derive(Debug, Deserialize, PartialEq, Eq)]
struct Session {
    id: String,
}

#[derive(Debug, PartialEq, Eq)]
struct Response {
    accounts: Vec<Account>,
    session: Session,
}

impl<'de> Deserialize<'de> for Response {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        use serde::de::{Error, SeqAccess, Visitor};

        #[derive(Deserialize)]
        enum Item {
            Account(Account),
            Session(Session),
        }

        struct ResponseVisitor;

        impl<'de> Visitor<'de> for ResponseVisitor {
            type Value = Response;

            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
                formatter.write_str("Response array structure")
            }

            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
            where
                A: SeqAccess<'de>,
            {
                let mut accounts = Vec::with_capacity(seq.size_hint().unwrap_or(0));

                while let Some(item) = seq.next_element()? {
                    match item {
                        Item::Account(account) => accounts.push(account),
                        Item::Session(session) => {
                            while let Some(item) = seq.next_element()? {
                                match item {
                                    Item::Account(account) => accounts.push(account),
                                    Item::Session(_session) => {
                                        return Err(Error::duplicate_field("Session"))
                                    }
                                }
                            }

                            return Ok(Response { accounts, session });
                        }
                    }
                }

                Err(Error::missing_field("Session"))
            }
        }

        deserializer.deserialize_seq(ResponseVisitor)
    }
}

#[test]
fn test() {
    let value = json!([
        {
            "Account": {
                "id": 0,
            }
        },
        {
            "Account": {
                "id": 1,
            }
        },
        {
            "Session": {
                "id": "hello!"
            }
        },
    ]);

    let response: Response = serde_json::from_value(value).unwrap();
    assert_eq!(
        response,
        Response {
            accounts: vec![Account { id: 0 }, Account { id: 1 },],
            session: Session {
                id: "hello!".to_string()
            }
        }
    );
}

@dtolnay
Copy link
Member

dtolnay commented Jan 23, 2022

If you're still looking for guidance on this, please take this question to one of the resources listed in https://github.com/serde-rs/serde/tree/v1.0.135#getting-help.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

No branches or pull requests

3 participants