GoでXMLにもJSONにもできる構造体を作る

  • 0
    Like
  • 0
    Comment

    goの構造体のタグは、スペース区切りで複数つけることができる。
    これを利用して、encoding/xmlのMarshalにも、encoding/jsonのMarshalにも対応した構造体を作ることができる。

    package main
    
    import (
        "encoding/json"
        "encoding/xml"
        "fmt"
    )
    
    type Foo struct {
        XMLName xml.Name `json:"-"`
        Aaa     []string `xml:"aaa" json:"aaa"`
        Bbb     []string `xml:"bbb" json:"bbb"`
    }
    
    func main() {
        foo := Foo{
            XMLName: xml.Name{"", "foo"},
            Aaa: []string{
                "12345", "67890",
            },
            Bbb: []string{
                "ABCDE", "FGHIJ",
            },
        }
        fmt.Println(foo)
    
        x, _ := xml.MarshalIndent(foo, "", "  ")
        fmt.Println(string(x))
    
        j, _ := json.MarshalIndent(foo, "", "  ")
        fmt.Println(string(j))
    }
    

    実行結果:

    $ go run xml.go 
    {{ foo} [12345 67890] [ABCDE FGHIJ]}
    <foo>
      <aaa>12345</aaa>
      <aaa>67890</aaa>
      <bbb>ABCDE</bbb>
      <bbb>FGHIJ</bbb>
    </foo>
    {
      "aaa": [
        "12345",
        "67890"
      ],
      "bbb": [
        "ABCDE",
        "FGHIJ"
      ]
    }
    

    解説

    複数のタグ

    見ての通り、構造体タグは複数個指定することができる。

    `xml:"aaa" json:"aaa"`
    

    簡単なマッピングであれば、1つの構造体定義を使いまわしてxml表現およびjson表現を設定できる。

    タグの設定は必要なの?

    構造体にタグを設定していない場合、Marshal時の表現名は構造体と完全一致になる。

    Goではpublicプロパティは名称の先頭が大文字にしなければならないという制約がある。この挙動で困る場合は明示的にタグを書くと良い。

    XMLNameってなんぞ

    XMLにはルート要素があり、ルート要素に名前を持っている。(この場合だと foo )
    これをencoding/xmlでは XMLName という特殊な名前の構造体要素に当てはめるようにしている。型はxml.Nameの実体である必要がある。

    一方で、JSONの場合この概念は不要なので、タグで json:"-" を指定することで無視させている。(ハイフン一文字だと要素を無視する意味になる)

    まとめ

    似たような感じで、DBでの保存形式を構造体のタグで表現するORマッパーがあるかもしれない。
    その場合も、1つの構造体で定義することができると思う。