Serialize/Deserialize json array to struct

1.8k Views Asked by At

I have incoming data in a json array that I deserialize into a struct, but I can't seem figure out how to serialize it back into an array instead of an object.

Do I have to implement a custom serializer here or is there some other serde attribute I can add?

I have looked through the documentation on serde.rs but I can't seem to find anything that would solve this.

#[cfg(test)]
mod tests {

    use serde_json;

    #[test]
    fn test_new() {
        #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
        #[serde(rename_all = "camelCase", untagged)]
        enum Kind {
            Authorization(AuthorizationKind),
            Notification(NotificationKind),
        }

        #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
        #[serde(rename_all = "camelCase", untagged)]
        enum AuthorizationKind {
            Request(AuthorizationRequest),
            Response(AuthorizationResponse),
        }

        #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
        #[serde(rename_all = "camelCase")]
        struct AuthorizationRequest {
            request: String,
            code: usize,
            secret: String,
        }

        #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
        #[serde(rename_all = "camelCase")]
        struct AuthorizationResponse {
            response: String,
            code: usize,
            authorized: bool,
            token: Token,
        }

        #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
        #[serde(rename_all = "camelCase")]
        struct Token {
            token: String,
        }

        #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
        #[serde(rename_all = "camelCase")]
        enum NotificationKind {
            Request(NotificationRequest),
            Response(NotificationResponse),
        }

        #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
        #[serde(rename_all = "camelCase")]
        struct NotificationRequest {
            request: String,
            code: usize,
        }

        #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
        #[serde(rename_all = "camelCase")]
        struct NotificationResponse {
            response: String,
        }

        let json = r#"["authorize request incoming",2,"TH15 15 MY! 53CR3T"]"#;

        let request: Result<Kind, _> = serde_json::from_str(&json);
        assert_eq!(request.is_ok(), true);

        let request = request.unwrap();
        let back_to_json = serde_json::to_string(&request).unwrap();
        println!("{back_to_json}");

        assert_eq!(json, back_to_json);
    }
}

Edit 1: I ended up implementing a custom serializer, not sure it's the best approach though.

#[cfg(test)]
mod tests {

    #[test]
    fn test_new() {
        #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
        #[serde(rename_all = "camelCase", untagged)]
        enum Kind {
            Authorization(AuthorizationKind),
            Notification(NotificationKind),
        }

        #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
        #[serde(rename_all = "camelCase", untagged)]
        enum AuthorizationKind {
            Request(AuthorizationRequest),
            Response(AuthorizationResponse),
        }

        #[derive(serde::Deserialize, Debug, Clone, PartialEq)]
        #[serde(rename_all = "camelCase")]
        struct AuthorizationRequest {
            request: String,
            code: usize,
            secret: String,
        }

        impl TryFrom<Kind> for AuthorizationRequest {
            type Error = &'static str;
            fn try_from(value: Kind) -> Result<Self, Self::Error> {
                match value {
                    Kind::Authorization(auth) => match auth {
                        AuthorizationKind::Request(req) => Ok(AuthorizationRequest {
                            request: req.request,
                            code: req.code,
                            secret: req.secret,
                        }),
                        _ => Err("Failed to create type"),
                    },
                    _ => Err("Failed to create type"),
                }
            }
        }

        impl serde::Serialize for AuthorizationRequest {
            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
            where
                S: serde::Serializer,
            {
                (&self.request, self.code, &self.secret).serialize(serializer)
            }
        }

        #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
        #[serde(rename_all = "camelCase")]
        struct AuthorizationResponse {
            response: String,
            code: usize,
            authorized: bool,
            token: Token,
        }

        #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
        #[serde(rename_all = "camelCase")]
        struct Token {
            token: String,
        }

        #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
        #[serde(rename_all = "camelCase")]
        enum NotificationKind {
            Request(NotificationRequest),
            Response(NotificationResponse),
        }

        #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
        #[serde(rename_all = "camelCase")]
        struct NotificationRequest {
            request: String,
            code: usize,
        }

        #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
        #[serde(rename_all = "camelCase")]
        struct NotificationResponse {
            response: String,
        }

        let json = r#"["authorize request incoming",2,"TH15 15 MY! 53CR3T"]"#;

        let request: Result<Kind, _> = serde_json::from_str(&json);
        assert_eq!(request.is_ok(), true);

        let request = request.unwrap();
        let authorization_request: AuthorizationRequest = request.try_into().unwrap();
        let back_json = serde_json::to_string(&authorization_request).unwrap();
        assert_eq!(json, back_json);
    }
}

Edit 3

Even better is to change AuthorizationKind and NotificationKind types to tuples. This will make it easy to serialize into a json array.

        #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
        #[serde(rename_all = "camelCase", untagged)]
        enum AuthorizationKind {
            Request(String, usize, String),
            Response(String, usize, bool, Token),
        }
...
        #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
        #[serde(rename_all = "camelCase")]
        enum NotificationKind {
            Request(String, usize),
            Response(String),
        }
1

There are 1 best solutions below

1
On

The problem is relying in the ordering, you would have to keep track of the keys (I'm adding an example but you could chose something else), then you can turn a json::Value (object) into an array iterating over the keys:


#[cfg(test)]
mod tests {

    use serde_json;

    #[test]
    fn test_new() {
        #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
        #[serde(rename_all = "camelCase", untagged)]
        enum Kind {
            Authorization(AuthorizationKind),
            Notification(NotificationKind),
        }

        impl Kind {
            fn keys(&self) -> Vec<&str> {
                match self {
                    Self::Authorization(a) => a.keys(),
                    Self::Notification(n) => n.keys(),
                }
            }
        }

        #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
        #[serde(rename_all = "camelCase", untagged)]
        enum AuthorizationKind {
            Request(AuthorizationRequest),
            Response(AuthorizationResponse),
        }

        impl AuthorizationKind {
            fn keys(&self) -> Vec<&str> {
                match self {
                    Self::Request(r) => r.keys(),
                    Self::Response(r) => r.keys(),
                }
            }
        }

        #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
        #[serde(rename_all = "camelCase")]
        struct AuthorizationRequest {
            request: String,
            code: usize,
            secret: String,
        }

        impl AuthorizationRequest {
            fn keys(&self) -> Vec<&str> {
                vec!["request", "code", "secret"]
            }
        }

        #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
        #[serde(rename_all = "camelCase")]
        struct AuthorizationResponse {
            response: String,
            code: usize,
            authorized: bool,
            token: Token,
        }

        impl AuthorizationResponse {
            fn keys(&self) -> Vec<&str> {
                vec!["response", "code", "authorized", "token"]
            }
        }

        #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
        #[serde(rename_all = "camelCase")]
        struct Token {
            token: String,
        }

        #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
        #[serde(rename_all = "camelCase")]
        enum NotificationKind {
            Request(NotificationRequest),
            Response(NotificationResponse),
        }

        impl NotificationKind {
            fn keys(&self) -> Vec<&str> {
                todo!()
            }
        }

        #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
        #[serde(rename_all = "camelCase")]
        struct NotificationRequest {
            request: String,
            code: usize,
        }

        impl NotificationRequest {
            fn keys(&self) -> Vec<&str> {
                todo!()
            }
        }

        #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
        #[serde(rename_all = "camelCase")]
        struct NotificationResponse {
            response: String,
        }

        impl NotificationResponse {
            fn keys(&self) -> Vec<&str> {
                todo!()
            }
        }

        let json = r#"["authorize request incoming",2,"TH15 15 MY! 53CR3T"]"#;

        let request: Result<Kind, _> = serde_json::from_str(&json);
        assert_eq!(request.is_ok(), true);

        let request = request.unwrap();
        let back_to_json = serde_json::to_value(&request).unwrap();
        let as_array = serde_json::to_string(
            &request
                .keys()
                .iter()
                .map(|k| back_to_json.get(k).unwrap())
                .collect::<Vec<_>>(),
        )
        .unwrap();
        println!("{as_array}");

        assert_eq!(json, as_array);
    }
}

Playground

It is not the best solution (but a working one), a lot of thing could go wrong on mismatches and refactors. If possible serialize your structures as objects instead of arrays.