Home > Blockchain >  unmarshal xml to []structs with xml.Name
unmarshal xml to []structs with xml.Name

Time:05-02

Problem

I need to unmarshal XML from an 3rd party application to my GO struct.

what i tried

I created structs and am able to generate identical XML from my code. @see TestMetaData_Marshal

ValidXML

i simplified the xml for this example:

<Parent>
    <Name>tic.TEST</Name>
    <MetaData>
        <LawFormID Type="129" DefinedSize="3">DOO</LawFormID>
        <FirmUQ Type="129" DefinedSize="15">BARTOG</FirmUQ>
        <FirmDescription Type="200" DefinedSize="100">Bartog d.o.o. Trebnje</FirmDescription>
    </MetaData>
</Parent>

Structs

my corresponding structs are the following. NOTE: i created a struct for MetaData though i would prefer to NOT use one (due json, yaml)

type Parent struct {
    Name string
    MetaData []Node `xml:"MetaData>any"`
}

type Node struct {
    XMLName     xml.Name // this allows custom node names
    Type        int      `xml:"Type,attr,omitempty"`
    DefinedSize int      `xml:"DefinedSize,attr,omitempty"`
    Text        string   `xml:",chardata"` // required at some places, cannot omit
}

@TestMetadata_Marshal


func TestMetaData_Marshal(t *testing.T) {
    parent := Parent{
        Name: "tic.TEST",
        MetaData: []Node{
            {
                XMLName: xml.Name{
                    Local: "LawFormID",
                },
                Text:        "DOO",
                Type:        129,
                DefinedSize: 3,
            }, {
                XMLName: xml.Name{
                    Local: "FirmUQ",
                },
                Text:        "BARTOG",
                Type:        129,
                DefinedSize: 15,
            }, {
                XMLName: xml.Name{
                    Local: "FirmDescription",
                },
                Text:        "Bartog d.o.o. Trebnje",
                Type:        200,
                DefinedSize: 100,
            },
        }, // \MetaData
    }
    HaveXML := XML(parent)
    fmt.Println(HaveXML)
    if ValidXML != HaveXML {
        t.Error(fmt.Errorf("marshaled XML did not match. Have: %v", HaveXML))
    }
}

Unmarshal

this is where the troubles begin; the test func is trivial; result is that no entries in MetaData are available.
I tried a lot of different tags, also tried creating an own MetaData struct containing Items []Node

func TestUnmarshal() {
    var parent Parent
    err := xml.Unmarshal([]byte(ValidXML), &parent)
    if err != nil {
        fmt.Println(err)
        return
    }
    HaveXML := XML(parent)
    if ValidXML == HaveXML {
        fmt.Println("\tmarshaled XML is Valid")
    } else {
        fmt.Printf("\tmarshaled XML did not match. Have:\n%v\n", HaveXML)
    }
}

go play (turned tests into 'normal' funcs

https://goplay.tools/snippet/kWS29HR7O9t

Question

Is it not possible to do this in go&xml? Iff it IS possible, what am i doing wrong?

CodePudding user response:

The problem is that any is not the tag name, it's an option. So when using it, you have to put a comma before it, e.g. xml:",any".

I don't think you can combine it with >, so create a separate MetaData struct:

type Parent struct {
    Name     string
    MetaData MetaData `xml:"MetaData"`
}

type MetaData struct {
    Nodes []Node `xml:",any"`
}

With this refactoring your example works and outputs (try it on the Go Playground):

=== TestMarshal
<Parent>
    <Name>tic.TEST</Name>
    <MetaData>
        <LawFormID Type="129" DefinedSize="3">DOO</LawFormID>
        <FirmUQ Type="129" DefinedSize="15">BARTOG</FirmUQ>
        <FirmDescription Type="200" DefinedSize="100">Bartog d.o.o. Trebnje</FirmDescription>
    </MetaData>
</Parent>
    marshaled XML is Valid
=== TestUnmarshal
    marshaled XML is Valid
  • Related