Unmarshal nested XML value from dynamic XML document

585 views
Skip to first unread message

Steffen Wentzel

unread,
Feb 23, 2019, 6:50:40 AM2/23/19
to golang-nuts
Hi all,

although I'm sure this question came up already I wasn't able to find a solution for my issue.

I have this example XML structure:

<outer>
 
<req4711response>
   
<return>{D4564-4564-456-4564456}</return>
 
</req4711response>
</outer>

I want to unmarshal the ID value contained in the <return> XML tags. However this XML structure is dynamic, the embedding XML tag (in this case <req4711response>) changes with every request.

How can I unmarshal the return value universally?

I have setup a little example: https://play.golang.org/p/j4ttdgW4W77

Also I've read this paragraph in the package documentation:

* If the XML element contains a sub-element whose name matches
   a struct field's XMLName tag and the struct field has no
   explicit name tag as per the previous rule, unmarshal maps
   the sub-element to that struct field.

I dont' know what is meant with "XMLName tag" in this context, but I think it might think it might point to a solution?

Thanks for any suggestions and hints,
Steffen

Tamás Gulácsi

unread,
Feb 23, 2019, 2:53:07 PM2/23/19
to golang-nuts
Walk over it usin xml.Decoder.Token, and UnmarshalElement when found a StartToken with a proper Name.Local.

Steffen Wentzel

unread,
Feb 24, 2019, 12:28:37 PM2/24/19
to golang-nuts
Thanks for the response. I was hoping it would be possible with just xml.Unmarshal.

I now have this implementation - any comments on that? (I'm okay with returning the error inline, it's just for logging purposes):


func id2(axl []byte) string {
dec := xml.NewDecoder(bytes.NewReader(axl))
for {
tok, err := dec.Token()
if err != nil {
if err == io.EOF {
return "token not found"
}
return fmt.Sprintf("error decoding XML: %v", err)

}
if tok, ok := tok.(xml.StartElement); ok {
if tok.Name.Local == "return" {
var id string
err := dec.DecodeElement(&id, &tok)
if err != nil {
return fmt.Sprintf("error decoding return element: %v", err)
}
return id
}
}
}
}

Sam Whited

unread,
Feb 24, 2019, 1:13:57 PM2/24/19
to 'Константин Иванов' via golang-nuts
Your function appears to return error text, but it will be much more clear what's going on when reading the code if you encode your intent in the return type and return an error instead of a string (which gives me no information about what that string is supposed to represent: is it an error? is it some XML?). fmt.Errorf can be used for this, also just return the io.EOF directly.

—Sam
> --
> You received this message because you are subscribed to the Google
> Groups "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to golang-nuts...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.
>

--
Sam Whited

Matt Harden

unread,
Feb 24, 2019, 11:44:34 PM2/24/19
to Steffen Wentzel, golang-nuts
You don't have to use an xml.Decoder. You may be able to use the `xml:",any"` tag for this case.

type Response struct {
XMLName xml.Name
Return  string `xml:"return"`
}
type Outer struct {
XMLName   struct{}   `xml:"outer"`
Responses []Response `xml:",any"`
}

https://play.golang.org/p/j4w4-1uuYae



--

Steffen Wentzel

unread,
Feb 25, 2019, 1:04:01 AM2/25/19
to golang-nuts
Very nice, `xml:",any"` was doing the trick. I've condensed the function and here's the (currently) final result: https://play.golang.org/p/G1eGw5gtk7C

func id3(axl []byte) string {
type Return struct {
Response struct {
Return string `xml:"return"`
} `xml:",any"`
}
var ret Return
err := xml.Unmarshal(axl, &ret)
if err != nil {
return fmt.Sprintf("unable to unmarshal id: %v", err) // I know I should use a separate error channel
}
return ret.Response.Return // might be empty if XML was invalid (no return element)
}

Thanks for all your comments!
Reply all
Reply to author
Forward
0 new messages