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

Deserializing an untagged enum fails on what looks like a correct input? #2447

Closed
frnsys opened this issue May 8, 2023 · 3 comments
Closed

Comments

@frnsys
Copy link

frnsys commented May 8, 2023

I have an untagged enum I'm trying to deserialize to. One of the variants looks for an optional field called m_Father. I expect the data in test.yml (below) to match this variant, but instead I get the error data did not match any variant of untagged enum Component. However if I re-write the enum variant as a struct, it deserializes correctly. I'm not sure if I'm overlooking a step or if something isn't working correctly.

Here is a minimal reproduction case:

use std::fs;
use serde::Deserialize;

#[derive(Debug, Deserialize)]
struct ExtReference {
    guid: String,
}

#[derive(Debug, Deserialize)]
struct IntReference {
    #[serde(rename="fileID")]
    id: String,
}

#[derive(Debug, Deserialize)]
#[serde(untagged)]
enum Component {
    Custom {
        #[serde(rename="m_Script")]
        script: ExtReference
    },

    BuiltIn {
        #[serde(skip)]
        name: String,

        #[serde(rename="m_Father")]
        parent: Option<IntReference>,
    },
}

#[derive(Debug, Deserialize)]
struct BuiltIn {
    #[serde(skip)]
    name: String,

    #[serde(rename="m_Father")]
    parent: Option<IntReference>,
}

fn main() {
    let path = "test.yml";
    let contents = fs::read_to_string(&path)
        .expect(&format!("Unable to read file {:?}", &path));

    // Works
    let alt: BuiltIn = serde_yaml::from_str(&contents).unwrap();
    println!("{:?}", alt);

    // Doesn't work
    let data: Component = serde_yaml::from_str(&contents).unwrap();
    println!("{:?}", data);
}

Contents of test.yml:

m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1121606776}
m_LocalRotation: {x: 0.72600275, y: -0.25875553, z: 0.0057348483, w: 0.63712853}
m_LocalPosition: {x: 0, y: 3, z: 2.95}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 2
m_LocalEulerAnglesHint: {x: 68.138, y: -120.335, z: -98.373}
@frnsys
Copy link
Author

frnsys commented May 8, 2023

So I think I figured out a solution, which is great, but I'm still confused about why one works and the other doesn't.

I was able to get it working by changing:

#[derive(Debug, Deserialize)]
struct IntReference {
    #[serde(rename="fileID")]
    id: String,
}

to:

#[derive(Debug, Deserialize)]
struct IntReference {
    #[serde(rename="fileID")]
    id: usize,
}

i.e. defining id as an integer. I don't understand why the struct version can cast the integer to string, but the enum version can't.

@Mingun
Copy link
Contributor

Mingun commented May 8, 2023

This happens because conversion from YAML int to Rust String is done by the yaml deserializer. However, untagged enums deserialized from an intermediate deserializer, that buffers content and of course does not do such conversion, because it is specific for YAML. In other words, you suffer from #1183.

Bufferization is used because serde need to try each variant, but deserialization consumes the input. So need a way to provide the same input several times.

@frnsys
Copy link
Author

frnsys commented May 9, 2023

I see, thank you for explaining. I'll close this because I at least managed to figure out a solution for my particular case.

@frnsys frnsys closed this as completed May 9, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

2 participants