Reputation: 135
So I've got this specific format for xml (industry standard) and I'm trying to create a simple program to allow us to make samples of this xml to test our services. I'm using the standard Go XML library.
Problem is the XML is annoyingly formatted. Here's the simplified version of it:
<Document>
<SubDocument>
{other fields}
<component>
<section>
<id value="0" valueType="num"/> //This is the part that differentiates the type of the section
<title>Foo-Type Section</title>
{other fields you lot don't need to care about}
</section>
</component>
<component>
<section>
<id value="1" valueType="num"/>
<title>Bar-Type Section</title>
{more fields you don't need to care about, but most are different than above}
</section>
</component>
{more sections}
</SubDocument>
</Document>
What I'm struggling with is that in Go, the tags on each section need to be unique if they are different struct types.
I've the following Go code:
type HasID struct{
ID string `xml:"value,attr,omitempty"`
IDType string `xml:"valueType,attr,omitempty"`
}
type FooSection struct{
ID HasID `xml:"id,omitempty"`
Title string `xml:"title,omitempty"`
//Foo fields
}
type BarSection struct{
ID HasID `xml:"id,omitempty"`
Title string `xml:"title,omitempty"`
//Bar fields
}
type Document struct{
XMLName struct{} `xml:"Document,omitempty"`
//Other fields
Sections []interface{} `xml:"SubDocument>component>section,omitempty"`
}
I've also tried to have the Sections field have no tag and have both FooSection and BarSection have the
XMLName struct{} `xml:"component>section,omitempty"`
tag, to no avail. Furthermore, I've tried having Sections be an array of strings and then marshaled each section type, dumped those in and used the ",innerxml" tag, but then it escapes the "<", etc of the innerxml.
Does anyone know a way to do this in Go? The structs are written by me and are completely open to change if need be.
It might just be that I'm too entrenched in OO and am having trouble being Go-like.
Thanks!
Upvotes: 2
Views: 2499
Reputation: 8009
I don't know if this is a perfect answer but it's workable. The gist is to implement encoding/xml.Unmarshaler on the Component
type then inside that UnmarshalXML
method you unmarshal the raw data of the section into a temporary value and inspect its ID before deciding if you want to unmarshal it into a FooSection
or BarSection
These are the types I'm working with
type ID struct {
Value int `xml:"value,attr,omitempty"`
Type string `xml:"valueType,attr,omitempty"`
}
type Document struct {
Components []Component `xml:"SubDocument>component"`
}
type Component struct {
Section interface{} `xml:"section"`
}
type FooSection struct {
ID ID `xml:"id"`
Title string `xml:"title"`
Foo string `xml:"foo"`
}
type BarSection struct {
ID ID `xml:"id"`
Title string `xml:"title"`
Bar string `xml:"bar"`
}
Note that Component
is storing its Section
as just an interface{}
. That is kind of annoying because you'll have to type switch it whenever you want to use it so you can probably do something better with that.
Then the UnmarshalXML
method is here
func (c *Component) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
// tmp holds the data for this Component. We can only call d.DecodeElement
// once so we have to put it somewhere so it can be reused.
tmp := struct {
Data []byte `xml:",innerxml"`
}{}
if err := d.DecodeElement(&tmp, &start); err != nil {
return err
}
// which holds just enough information to tell us what kind of section to
// make. We'll unmarshal tmp.Data into this to inspect it
which := struct {
ID ID `xml:"id"`
}{}
if err := xml.Unmarshal(tmp.Data, &which); err != nil {
return err
}
switch which.ID.Value {
case 0:
var f FooSection
if err := xml.Unmarshal(tmp.Data, &f); err != nil {
return err
}
c.Section = f
case 1:
var b BarSection
if err := xml.Unmarshal(tmp.Data, &b); err != nil {
return err
}
c.Section = b
}
return nil
}
Full working code on the playground.
Edit: These types should also work for generating the XML string as you actually were asking. When you're constructing each Component
you should choose what kind of section to make and just stick it in there. Since it's a interface{}
it can hold anything. I've updated my playground link to an example that shows turning those types back into a string works as expected.
Upvotes: 1