L.D.
L.D.

Reputation: 135

Golang XML Creation with same tags

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

Answers (1)

jcbwlkr
jcbwlkr

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

Related Questions