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

#[serde(untagged)] ignores deserialize_struct in custom Deserialize #2607

Open
Rulexec opened this issue Sep 3, 2023 · 2 comments
Open

#[serde(untagged)] ignores deserialize_struct in custom Deserialize #2607

Rulexec opened this issue Sep 3, 2023 · 2 comments

Comments

@Rulexec
Copy link

Rulexec commented Sep 3, 2023

Hello.

I have some data format for which I had implemented Deserializer. It has deserialize_struct, where I have if name == "SOME_STRUCT", then I calling visitor.visit_map() with very specific MapAccess.

All was perfectly fine, until I put this struct inside untagged enum.

My struct have Deserialize implementation, which calls deserializer.deserialize_struct("SOME_STRUCT", &["SOME_FIELDS"], MyVisitor). And as I see, serde does not try to call corresponding method in my deserializer, but uses deserialize_any and then sends first value which he found to MyVisitor. But this value is from my general MapAccess implementation, not custom one for SOME_STRUCT.

Is there some workarounds to fix this issue, or maybe possibility to implement some attribute to alter mechanic of untagged enum?


I'll try to describe my original problem, maybe it will give some context and we can to do something more useful than continue to complicate untagged enums.

I actually parsing yaml, but I need to be able report line:column of errors, and even if it was parsed successfully, I need to have line:column markers, because after parsing I processing parsed data and there still can be semantic errors, which I want to report too, pointing to specific lines/columns.

I found this solution in toml crate: https://docs.rs/toml/0.5.6/src/toml/spanned.rs.html#35-42 (which I think will fail too if you will put Spanned<String> inside a untagged enum) and made mine in this style.

But actually, I think that there should be possibility to access internals of specific Deserializer instance from Deserialize, or ability to ask him somehow to return specific type even if this type do not implements Deserialize. I'll be happy, if I there will be Deserializer method like inspect_custom(&self, name: &str) -> Result<*const sometype, Error>, which I can call from Deserialize implementation and unsafely do my dark deeds if I needed to and current deserializer supports it. Maybe it possible to make more clean with some traits, but nothing comes to mind.


Thanks.

UPD: took a look at the sources, looks like all of this is impossible for untagged/internally tagged enums because of deserialization to Content which erases all information that has specific Deserializer :(

@Rulexec
Copy link
Author

Rulexec commented Sep 3, 2023

After taking a sleep got idea for my case. Since I parsing already not a string/bytes, but my Deserializer works already on my type YamlNode, I can just avoid of using #[derive(Deserialize] for all my enums and write their implementations by hand, like:

enum EnumWithRaw {
    Variant(String),
    Raw(YamlNode),
}

impl<'de> Deserialize<'de> for EnumWithRaw {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let raw: &YamlNode = Deserialize::deserialize(deserializer)?;

        if let Ok(value) = decode_yaml(raw) {
            return Ok(EnumWithRaw::Variant(value));
        }
        if let Ok(value) = decode_yaml(raw) {
            return Ok(EnumWithRaw::Raw(value));
        }

        Err(D::Error::custom("bad news"))
    }
}

And now I have enum that tries to parse to String, if it fails, then just returns underlying YamlNode for which I can print nice error, that in this context on specific line&column no suitable variant found.

But impl<'de> serde::de::Deserialize<'de> for &'de YamlNode is very scary, since currently it can be implemented only by very unsafe code:

MapAccess part:

let value = {
    let node = self.node;
    let ptr = node as *const YamlNode;
    let ptr = ptr as u64;
    ptr
}

let de = U64Deserializer::new(value);

seed.deserialize(de)

Visitor part:

if key != RAW_YAML_NODE_VALUE {
    return Err(A::Error::custom("YamlNodeVisitor: key order missmatch"));
}

let node = unsafe {
    let ptr = ptr as (*const YamlNode);
    &*ptr
};

Ok(node)

@Mingun
Copy link
Contributor

Mingun commented Sep 3, 2023

My PR #2569 solves the problem with deserialize_map

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