serde-xml-rs icon indicating copy to clipboard operation
serde-xml-rs copied to clipboard

Cannot Deserialize Recursive Elements

Open git-blame opened this issue 6 years ago • 11 comments

For example, consider a directory which can contain directories and files. We try to declare recursive enum and struct:

#[derive(Deserialize, Debug)]
#[serde(rename_all="lowercase")]
enum Inode {
    File {
        name: String,
        #[serde(rename(deserialize="$value"), default)]
        value: String,
    },
    Dir {
        name: String,
        #[serde(rename(deserialize="$value"), default)]
        inode: Vec<Box<Inode>>,
    }
}

#[derive(Deserialize, Debug)]
struct InodeEnumList {
    #[serde(rename="$value")]
    list: Vec<Inode>
}

#[derive(Deserialize, Debug)]
#[serde(rename_all="lowercase")]
struct InodeStruct {
    #[serde(default)]
    name: String,
    #[serde(rename(deserialize="$value"), default)]
    inode: Vec<Box<InodeStruct>>,
}

#[derive(Deserialize, Debug)]
struct InodeList {
    #[serde(rename="$value")]
    list: Vec<InodeStruct>
}
  • A recursive enum will correctly decode but only for 1 level. That is, the $value is not found by deserializer for child elements.
  • A recursive struct will correctly decode but only if there is no text. If there is text, there is an error. Furthermore, the type of the element is lost (eg, file vs. dir).

git-blame avatar Nov 22 '18 17:11 git-blame

Example output:

    <list>
        <file name="README.md"></file>
        <file name="LICENCE.md"></file>
        <dir name="empty" />
        <dir name="files" >
            <file name="main.rs"></file>
        </dir>
        <dir name="mix">
            <file name="lib.rs"></file>
            <dir name="test">
                <dir name="empty" />
                <file name="test.rs"></file>
                <dir name="subdir">
                    <file name="nested.rs"></file>
                </dir>
            </dir>
        </dir>
    </list>

InodeEnumList {
    list: [
        File {
            name: "README.md",
            value: ""
        },
        File {
            name: "LICENCE.md",
            value: ""
        },
        Dir {
            name: "empty",
            inode: []
        },
        Dir {
            name: "files",
            inode: []
        },
        Dir {
            name: "mix",
            inode: []
        }
    ]
}
InodeList {
    list: [
        InodeStruct {
            name: "README.md",
            inode: []
        },
        InodeStruct {
            name: "LICENCE.md",
            inode: []
        },
        InodeStruct {
            name: "empty",
            inode: []
        },
        InodeStruct {
            name: "files",
            inode: [
                InodeStruct {
                    name: "main.rs",
                    inode: []
                }
            ]
        },
        InodeStruct {
            name: "mix",
            inode: [
                InodeStruct {
                    name: "lib.rs",
                    inode: []
                },
                InodeStruct {
                    name: "test",
                    inode: [
                        InodeStruct {
                            name: "empty",
                            inode: []
                        },
                        InodeStruct {
                            name: "test.rs",
                            inode: []
                        },
                        InodeStruct {
                            name: "subdir",
                            inode: [
                                InodeStruct {
                                    name: "nested.rs",
                                    inode: []
                                }
                            ]
                        }
                    ]
                }
            ]
        }
    ]
}

    <list>
        <file name="README.md">README.md</file>
        <file name="LICENCE.md">LICENCE.md</file>
        <dir name="empty" />
        <dir name="files" >
            <file name="main.rs">main.rs</file>
        </dir>
        <dir name="mix">
            <file name="lib.rs">lib.rs</file>
            <dir name="test">
                <dir name="empty" />
                <file name="test.rs">test.rs</file>
                <dir name="subdir">
                    <file name="nested.rs">nested.rs</file>
                </dir>
            </dir>
        </dir>
    </list>

InodeEnumList {
    list: [
        File {
            name: "README.md",
            value: "README.md"
        },
        File {
            name: "LICENCE.md",
            value: "LICENCE.md"
        },
        Dir {
            name: "empty",
            inode: []
        },
        Dir {
            name: "files",
            inode: []
        },
        Dir {
            name: "mix",
            inode: []
        }
    ]
}
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: 
Error(UnexpectedToken("XmlEvent::StartElement { name, attributes, .. }", "Characters(README.md)"), 
State { next_error: None, backtrace: None })', libcore/result.rs:1009:5

git-blame avatar Nov 22 '18 17:11 git-blame

I have the same issue with:

<?xml version="1.0" encoding="UTF-8"?>
<ValueLookup id="123">
 <Lookups>
  <item state="None" value="0"  >Unknown</item>
  <item state="Ok"   value="1">It fails on this value</item>
 </Lookups>
</ValueLookup>


    UnexpectedToken(
        "XmlEvent::EndElement { name, .. }",
        "StartElement(SingleInt, {\"\": \"\", \"xml\": \"http://www.w3.org/XML/1998/namespace\", \"xmlns\": \"http://www.w3.org/2000/xmlns/\", \"xsi\": \"http://www.w3.org/2001/XMLSchema-instance\"}, [state -> Ok, value -> 1])",
    ),

It would also be awesome if the Error could indicate Line number and Column for the parsing errors. And perhaps the current buffer...

JRAndreassen avatar Feb 13 '20 18:02 JRAndreassen

@JRAndreassen I solved this issue by creating a struct for the root element, it seems that it's searching into the XML parser state but the end of because your ValueLookup hasn't been parsed.

SeedyROM avatar Jan 27 '21 08:01 SeedyROM

@SeedyROM ,...

Not sure I follow.... "A section of code is with a 1K words" :)

JRAndreassen avatar Jan 28 '21 15:01 JRAndreassen

Has this been answered, having same problem.

Capicou avatar Nov 17 '22 20:11 Capicou

@Capicou ... It's been so long I forget... I'll check when I got my head above water...

JRatPrtgconsultants avatar Nov 17 '22 21:11 JRatPrtgconsultants

Let me know, still have not figured out.

Capicou avatar Nov 21 '22 14:11 Capicou

@Capicou I am now using the crate quick-xml for XML deserialization but not for this specific issue. I wrote up a quick test and found that:

  • Latest serde-xml-rs (0.6.0) still has this issue
  • quick-xml (0.26.0) is able to correctly deserialize into recursive enum. It has problems with recursive structs but there may be a way to organize or add tags in such a way as to make it work.

recurse.zip

git-blame avatar Nov 22 '22 16:11 git-blame

Thanks, I had figured it out.

Capicou avatar Nov 23 '22 12:11 Capicou

@git-blame, the initial problem isn't really a bug. You can't use a plain String as the $value of an InodeStruct.

punkstarman avatar Nov 23 '22 13:11 punkstarman

@JRatPrtgconsultants (or @JRAndreassen is it?), to help with the problem you encountered, I would need the Rust code you were using.

I agree that the error message could be better.

punkstarman avatar Nov 23 '22 13:11 punkstarman