Search code examples
xmlgounmarshalling

Parse XML tag to boolean if it exists when unmarshalling


I'm trying to parse XML tag into a boolean if it exists. The tags inside <status> can be <active />, <available /> or <invalid /> and only one of these tag exists inside <status>.

Here's my current attempt:

package main

import (
    "encoding/xml"
    "fmt"
)

type Response struct {
    XMLName      xml.Name `xml:"domain"`
    Authority    string   `xml:"authority,attr"`
    RegistryType string   `xml:"registryType,attr"`
    EntityClass  string   `xml:"entityClass,attr"`
    EntityName   string   `xml:"entityName,attr"`
    DomainName   string   `xml:"domainName"`
    Status       Status   `xml:"status"`
}

type Status struct {
    Active    bool `xml:"active,omitempty"`
    Available bool `xml:"available,omitempty"`
    Invalid   bool `xml:"invalid,omitempty"`
}

func main() {

    str := `
<domain authority="domain.fi" registryType="dchk1" entityClass="domain-name" entityName="esimerkki.fi">
  <domainName>esimerkki.fi</domainName>
  <status>
    <active />
  </status>
</domain>
`

    var ans Response
    err := xml.Unmarshal([]byte(str), &ans)
    if err != nil {
        panic(err)
    }

    fmt.Printf(`%#v`, ans.Status)
}

This returns all Status.* as false and not Status.Active = true as expected. How can you get the expected result?

Second attempt with pointers:

type Status struct {
    Active    *bool `xml:"active,omitempty"`
    Available *bool `xml:"available,omitempty"`
    Invalid   *bool `xml:"invalid,omitempty"`
}

*ans.Status.Active still false.


Solution

  • As @mh-cbon suggested simply checking for if pointer wasn't nil for *bool was enough to determine if that tag existed. But I went and turned the XML response struct into proper Response struct which only contains needed information and Status into a constant.

    So now it's:

    // Raw XML DTO
    type XmlResponse struct {
        XMLName      xml.Name  `xml:"domain"`
        Authority    string    `xml:"authority,attr"`
        RegistryType string    `xml:"registryType,attr"`
        EntityClass  string    `xml:"entityClass,attr"`
        EntityName   string    `xml:"entityName,attr"`
        DomainName   string    `xml:"domainName"`
        Status       XmlStatus `xml:"status"`
    }
    
    // Raw XML DTO
    type XmlStatus struct {
        Active    *bool `xml:"active,omitempty"`
        Available *bool `xml:"available,omitempty"`
        Invalid   *bool `xml:"invalid,omitempty"`
    }
    
    // Return Response struct which doesn't have the unnecessary XML
    func (x XmlResponse) GetResponse() Response {
        st := Invalid // Default to invalid
    
        if x.Status.Active != nil {
            st = Active
        } else if x.Status.Available != nil {
            st = Available
        } else if x.Status.Invalid != nil {
            st = Invalid
        }
    
        return Response{
            Domain: x.DomainName,
            Status: st,
        }
    }
    
    // Proper response parsed from XML
    type Response struct {
        Domain string
        Status Status
    }
    
    type Status uint8
    
    const (
        Invalid Status = iota
        Active
        Available
    )
    

    And parsing happens as:

    var xmlresp XmlResponse
    err := xml.Unmarshal([]byte(str), &xmlresp)
    if err != nil {
        panic(err)
    }
    
    ans := xmlresp.GetResponse()
    
    fmt.Printf(`%#v`, ans)