Serde json flattening: missing field

Hello. I'm trying to flatten json response but got the missing field error. I want to collect some nested fields to a single struct in a vec. What am doing wrong?

#[derive(Serialize, Deserialize, Debug)]
struct AttachmentResponse {
    results: Vec<Attachment>,
}

#[derive(Serialize, Deserialize, Debug)]
struct Attachment {
    id: String,
    title: String,

    #[serde(flatten)]
    metadata: AttachmentMetadata,
    #[serde(flatten, rename = "_links")]
    links: AttachmentLinks,
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct AttachmentMetadata {
    media_type: String,
}

#[derive(Serialize, Deserialize, Debug)]
struct AttachmentLinks {
    download: String,
}

fn main() -> anyhow::Result<()> {
    let document = serde_json::json!({
    "results": [
        {
            "id": "719414246",
            "type": "attachment",
            "status": "current",
            "title": "Screenshot 2024-03-12 at 19.49.45.png",
            "metadata": {
                "mediaType": "image/png",
                "labels": {
                    "results": [],
                    "start": 0,
                    "limit": 200,
                    "size": 0,
                    "_links": {
                        "self": "https://confluence.my.com/rest/api/content/719414246/label"
                    }
                },
                "_expandable": {
                    "currentuser": "",
                    "frontend": "",
                    "editorHtml": "",
                    "properties": ""
                }
            },
            "extensions": {
                "mediaType": "image/png",
                "fileSize": 240734,
                "comment": ""
            },
            "_links": {
                "webui": "/display/~aaa/Files?preview=%2F719391738%2F719414246%2FScreenshot+2024-03-12+at+19.49.45.png",
                "download": "/download/attachments/719391738/Screenshot%202024-03-12%20at%2019.49.45.png?version=1&modificationDate=1710867778590&api=v2",
                "thumbnail": "/download/thumbnails/719391738/Screenshot%202024-03-12%20at%2019.49.45.png?api=v2",
                "self": "https://confluence.my.com/rest/api/content/719414246"
            },
            "_expandable": {
                "container": "/rest/api/content/719391738",
                "operations": "",
                "children": "/rest/api/content/719414246/child",
                "restrictions": "/rest/api/content/719414246/restriction/byOperation",
                "history": "/rest/api/content/719414246/history",
                "ancestors": "",
                "body": "",
                "version": "",
                "descendants": "/rest/api/content/719414246/descendant",
                "space": "/rest/api/space/~aaa"
            }
        },
    ],
    "start": 0,
    "limit": 100,
    "size": 5,
    "_links": {
        "self": "https://confluence.psbnk.msk.ru/rest/api/content/719391738/child/attachment",
        "base": "https://confluence.psbnk.msk.ru",
        "context": ""
    }
    })
    .to_string();

    let data: AttachmentResponse = serde_json::from_str::<AttachmentResponse>(&document)?.into();

    println!("{:?}", data.results[0]);

    Ok(())
}

1 Like

flatten doesn't do any collecting. It's just used to describe the layout of your data. Your metadata isn't flattened into the parent object as it is its own object in the metadata field. Actually, when you do deserialization, flatten works the exact opposite way, it doesn't allow you to collect sub-fields of the JSON into a higher-level Rust struct but allows you to collect JSON fields from the higher-level object into a sub-struct on the Rust side. Your JSON would need to look like this:

{
  "id": "719414246",
  "type": "attachment",
  "status": "current",
  "title": "Screenshot 2024-03-12 at 19.49.45.png",
  "mediaType": "image/png",
  "labels": {
    "results": [],
    "start": 0,
    "limit": 200,
    "size": 0,
    "_links": {
      "self": "https://confluence.my.com/rest/api/content/719414246/label"
    },
    ...
}

to make the flatten attribute work on metadata.

My suggestion would be to do the collecting in a two-step approach: (1) deserialize the JSON and (2) afterwards do the collecting in your code. It's easier to implement and reason about than writing your own Deserialize implementation (albeit probably less performant), which would be your other option.

Thank you for the explanation. I definitely incorrectly understood what flatten do.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.