Greg Nisbet
Greg Nisbet

Reputation: 6994

Golang specify top-level tag in xml structure

So I am trying to Unmarshal or Decode some xml into a type (I am still not entirely clear on what the difference is), and I don't seem to be able to specify the outermost type (in this case <people>). When I try to specify this tag, instead of getting an error, the Marshalled value does not contain any of the content I am expecting. How do you specify the outermost tag and why doesn't the second assignment have the expected behavior?

package main

import "fmt"
import "encoding/xml"
import "log"

var data string = `
<people>
  <person>
    <id>46</id>
    <name>John Smith</name>
  </person>
  <person>
    <id>3007</id>
    <name>Joe Smith</name>
  </person>
</people>
`

type Person struct {
    Id int `xml:"id"`
    Name string `xml:"name"`
}

type People struct {
    PersonList []Person `xml:"person"`
}

type Response struct {
    PeopleItem People `xml:"people"`
}

func main() {
    // parsing People
    // cannot specify outermost tag <people></people>
    var people People
    err := xml.Unmarshal([]byte(data), &people)

    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(people)
    // prints "{[{46 John Smith} {3007 Joe Smith}]}"
    // which is reasonable

    // attempting to parse entire response, yields struct of struct of empty slice
    var response Response
    err = xml.Unmarshal([]byte(data), &response)
    if err != nil {
        log.Fatal(err)
    }

    // response is struct of struct of empty array
    // why does this happen?
    fmt.Println(response)
    // why does this print "{{[]}}" ?
}

Upvotes: 3

Views: 1875

Answers (2)

Justin Ohms
Justin Ohms

Reputation: 3553

You can do this without hacking the incoming data, you use the special XMLName xml.Name field to set the outside tag and then use xml:",chardata" to access it's contents.

Here is an example: Try it on the go playground

package main

import (
    "encoding/xml"
    "fmt"
)

type simpleInt struct {
    XMLName xml.Name `xml:"integer"`
    Int     int      `xml:",chardata"`
}

type simpleString struct {
    XMLName xml.Name `xml:"stringtag"`
    String  string   `xml:",chardata"`
}

type simpleBoolean struct {
    XMLName xml.Name `xml:"boolean"`
    Boolean bool     `xml:",chardata"`
}

func main() {

    bint := []byte("<integer>1138</integer>")
    bstring := []byte("<stringtag>Caimeo</stringtag>")
    btrue := []byte("<boolean>true</boolean>")
    bfalse := []byte("<boolean>false</boolean>")

    i := simpleInt{}
    xml.Unmarshal(bint, &i)
    fmt.Println(i, i.Int)

    s := simpleString{}
    xml.Unmarshal(bstring, &s)
    fmt.Println(s, s.String)

    m := simpleBoolean{}
    xml.Unmarshal(btrue, &m)
    fmt.Println(m, m.Boolean)

    xml.Unmarshal(bfalse, &m)
    fmt.Println(m, m.Boolean)
}

Upvotes: 2

Gordon Childs
Gordon Childs

Reputation: 36179

Here's a lazy solution: add a new, unique top level element:

var response Response
err = xml.Unmarshal([]byte("<foo>"+data+"</foo>"), &response)

Upvotes: 1

Related Questions