Search code examples
xmlgostructxml-parsingunmarshalling

How to unmarshal dynamic XML data to struct?


I have a XML data which has some repeated data with dynamic element name as shown below.

<Ht>
<Criteria>
    <ADNode_1>2</ADNode_1>
    <CDNode_1>2</CDNode_1>
    <IFNode_1>0</IFNode_1>
    <ADNode_2>2</ADNode_2>
    <CDNode_2>0</CDNode_2>
    <IFNode_2>0</IFNode_2>
    <ADNode_3>0</ADNode_3>
    <CDNode_3>0</CDNode_3>
    <IFNode_3>0</IFNode_3>
</Criteria>
<session id="1056134770841202228344907">
    <Htd ID="21170">
        <Data_1>
            <Info Count="2"></Info>
            <Data Id="iV29308/B2/R1">
                <Type>TR1</Type>
            </Data>
            <Data Id="iV29308/B3/R1">
                <Type>TR1</Type>
            </Data>
            <Data Id="iV29308/B4/R1">
                <Type>TR1</Type>
            </Data>
            <Data Id="iV29308/B6/R1">
                <Type>TR1</Type>
            </Data>
        </Data_1>
        <Data_2>
            <Info Count="2"></Info>
            <Data Id="iV29308/B2/R1">
                <Type>TR2</Type>
            </Data>
            <Data Id="iV29308/B3/R1">
                <Type>TR2</Type>
            </Data>
            <Data Id="iV29308/B4/R1">
                <Type>TR2</Type>
            </Data>
            <Data Id="iV29308/B6/R1">
                <Type>TR3</Type>
            </Data>
        </Data_2>
    </Htd>
</session>

I can create individual structs for <ADNode_1> ,<ADNode_2> and <ADNode_3> or <Data_1>, <Data_2> but there can be n number of these nodes. Like below

<ADNode_1>2</ADNode_1>
<ADNode_2>2</ADNode_2>
<ADNode_3>2</ADNode_3>
<ADNode_n>2</ADNode_n>

OR

<Data_1></Data_1>
<Data_2></Data_2>
<Data_3></Data_3>
<Data_n></Data_n>

How can I create struct for these nodes to have n number of nodes or elements?

Here is the playground link I am trying to work with.


Solution

  • Generally for these situations you may use a slice in Go to "gather" the elements, and use the ,any option to put everything in it you don't have a mapping for. To be able to identify the source, use an XMLName xml.Name field which will retain the name of the XML tag it originates from.

    For example you may model your XML like this:

    type Ht struct {
        Criteria struct {
            Nodes []struct {
                XMLName xml.Name
                Content string `xml:",chardata"`
            } `xml:",any"`
        }
        Session struct {
            ID  string `xml:"id,attr"`
            Htd struct {
                ID    string `xml:"ID,attr"`
                DataX []struct {
                    XMLName xml.Name
                    Info    struct {
                        Count int `xml:"Count,attr"`
                    }
                    DataNodes []struct {
                        XMLName xml.Name
                        ID      string `xml:"Id,attr"`
                        Type    string
                    } `xml:",any"`
                } `xml:",any"`
            }
        } `xml:"session"`
    }
    

    Parsing it and reencoding it will retain all elements:

    var ht Ht
    if err := xml.Unmarshal([]byte(src), &ht); err != nil {
        panic(err)
    }
    
    result, err := xml.MarshalIndent(ht, "", "  ")
    if err != nil {
        panic(err)
    }
    
    fmt.Println(string(result))
    

    This will output (try it on the Go Playground):

    <Ht>
      <Criteria>
        <ADNode_1>2</ADNode_1>
        <CDNode_1>2</CDNode_1>
        <IFNode_1>0</IFNode_1>
        <ADNode_2>2</ADNode_2>
        <CDNode_2>0</CDNode_2>
        <IFNode_2>0</IFNode_2>
        <ADNode_3>0</ADNode_3>
        <CDNode_3>0</CDNode_3>
        <IFNode_3>0</IFNode_3>
      </Criteria>
      <session id="1056134770841202228344907">
        <Htd ID="21170">
          <Data_1>
            <Info Count="2"></Info>
            <Data Id="iV29308/B2/R1">
              <Type>TR1</Type>
            </Data>
            <Data Id="iV29308/B3/R1">
              <Type>TR1</Type>
            </Data>
            <Data Id="iV29308/B4/R1">
              <Type>TR1</Type>
            </Data>
            <Data Id="iV29308/B6/R1">
              <Type>TR1</Type>
            </Data>
          </Data_1>
          <Data_2>
            <Info Count="2"></Info>
            <Data Id="iV29308/B2/R1">
              <Type>TR2</Type>
            </Data>
            <Data Id="iV29308/B3/R1">
              <Type>TR2</Type>
            </Data>
            <Data Id="iV29308/B4/R1">
              <Type>TR2</Type>
            </Data>
            <Data Id="iV29308/B6/R1">
              <Type>TR3</Type>
            </Data>
          </Data_2>
        </Htd>
      </session>
    </Ht>