XML,JSON,YAML のGo言語による操作について調べた結果

データ交換やAPIの利用などで、YAML,JSON,XMLなどのエンコードとデコード処理は必須だ。そこで、Go言語初学者なので、これらの処理方法を調べてみたメモである。この記事に、間違いや、もっと良い方法があれば、教えてください。

## パッケージ共通的機能
XML,JSON,YAMLなどを扱うパッケージは、以下の共通的な機能を持っている。
* XML   https://pkg.go.dev/encoding/xml
* JSON  https://pkg.go.dev/encoding/json
* YAML  https://pkg.go.dev/gopkg.in/yaml.v2


### 共通的機能 Marshal/Unmarshal
バイト型配列を対象として機能する。

Unmarshalは、XML,JSON,YAMLのバイト配列([]byte)を構造体型へ格納する。
* func Unmarshal(data []byte, v any) error

Marshalは構造体型のデータから、XML,JSON,YAMLのバイト配列([]byte)を返す。
* func Marshal(v any) ([]byte, error)
* func MarshalIndent(v any, prefix, indent string) ([]byte, error)


### 共通的機能 Encoder/Decoder
ファイルなどのI/Oストリームから読み込む、または、書き込むのと合わせて、変換処理する。

Decoderは、XML,JSON,YAMLなどのファイルを読み込んで、構造体型へ格納する。
* func NewDecoder(r io.Reader) *Decoder
* func (dec *Decoder) Decode(v interface{}) (err error)

Encoderは、構造体型から、XML,JSON,YAMLへ書出す。
* func NewEncoder(w io.Writer) *Encoder
* func (e *Encoder) Encode(v interface{}) (err error)



## XMLについて
XMLファイルの読み込みと書き出しについて、Marshal/Unmarshal、Encoder/Decoder　の４パターンについて、以下の示す。このパッケージが機能するためには、XML名前フィールドに対応づけたタグを構造体型に追記しなければならない。具体的なタグの書き方は、パッケージのドキュメントを参照願う。

### XML Decord (XMLファイルから構造体へ)

~~~
package main
import (
        "encoding/xml"
        "os"
)
func readXml(fn string, xf interface{}) error {
        file, err := os.Open(fn)
        if err != nil {
                return err
        }
        defer file.Close()
        decoder := xml.NewDecoder(file)
        err = decoder.Decode(xf)　// メモリ上の構造体へ展開
        if err != nil {
                return err
        }
        return nil
}
~~~

### XML Encode (構造体からXMLファイルへ)
XMLファイルにインデントしないと見にくいので、`encoder.Indent("","    ")`で設定した。

~~~
package main
import (
        "encoding/xml"
        "os"
)
func writeXml(fn string, xf interface{}) error {
        file, err := os.Create(fn)
        if err != nil {
                return err
        }
        defer file.Close()
        encoder := xml.NewEncoder(file)
        encoder.Indent("","    ")
        err = encoder.Encode(xf)　
        if err != nil {
                return err
        }
        return nil
}
~~~

### XML Unmarshal (メモリ上のXMLから構造体へ)

~~~
package main
import (
        "encoding/xml"
        "io"
        "os"
)
func readXml(fn string, xf interface{}) error {
        file, err := os.Open(fn)
        if err != nil {
                return err
        }
        defer file.Close()
        byteValue, err := io.ReadAll(file)
        if err != nil {
                return err
        }
        err = xml.Unmarshal(byteValue, xf)　// 構造体へ展開
        if err != nil {
                return err
        }
        return nil
}
~~~

### XML Marshal　(構造体からメモリ上のXMLへ)
こちらも、人間が目で読みやすい様に、`xml.MarshalIndent(xf, "", "    ")`でインデントを設定する。

~~~
package main
import (
        "encoding/xml"
        "os"
)
func writeXml(fn string, xf interface{}) error {
        file, err := os.Create(fn)
        if err != nil {
                return err
        }
        defer file.Close()
        output, err := xml.MarshalIndent(xf, "", "    ")
        if err != nil {
                return err
        }
        file.Write(output)
        return nil
}
~~~


## JSONについて
JSONパッケージは、JSONファイルのキー名と、構造体の要素変数名が一致してれば、タグを記述する必要が無いので便利だ。

### JSON Decord (JSONファイルから構造体へ)
複数のJSON行を読み、構造体型の入れるへ格納する。この様な処理では、Interface型が利用できないので、注意しなければならない。

~~~
package main
import (
        "os"
        "io"
        "encoding/json"
)
// パラメータ mems の型は、対象に合わせて要変更
func readJson(fn string, mems *[]Member) (error) {
        file,err := os.Open(fn)
        if err != nil {
                return err
        }
        defer file.Close()
        // 行単位に読んで、構造体型の配列へセット
        dec := json.NewDecoder(file)　
        for {
                var m Member
                if err := dec.Decode(&m); err == io.EOF {
                        break
                } else if err != nil {
                        return err
                }
                *mems = append(*mems, m)　//配列へ要素追加
        }
        return nil
}
~~~

### JSON Encode (構造体からJSONファイルへ)
構造体型を一括でファイルへ書出す。こちらはインタフェース型を利用できる。

~~~
package main
import (
        "os"
        "encoding/json"
)
func writeJson(fn string, jf interface{}) (error) {
        file,err := os.Create(fn)
        if err != nil {
                return err
        }
        defer file.Close()
        encoder := json.NewEncoder(file)
        encoder.SetIndent("", "    ")
        err = encoder.Encode(jf)　// ストリームへJSON形式で書き出し
        if err != nil {
                return err
        }
        return nil
}
~~~

### JSON Unmarshal (メモリ上のJSONから構造体へ)
配列形式のJSONデータを一括で読み込み。

~~~
package main
import (
        "os"
        "io"
        "encoding/json"
)
func readJson(fn string, jf interface{}) (error) {
        file,err := os.Open(fn)
        if err != nil {
                return err
        }
        defer file.Close()
        byteJSON,err := io.ReadAll(file)
        if err != nil {
                return err
        }
        err = json.Unmarshal(byteJSON,jf)　// メモリ上のJSONを構造体へ展開
        if err != nil {
                return err
        }
        return nil
}
~~~

### JSON Marshal　(構造体からメモリ上のJSONへ)
人が読む必要がある時は、`json.MarshalIndent(jf,"","    ")`によってインデントを付けて書き込みができる。

~~~
package main
import (
        "os"
        "encoding/json"
)
func writeJson(fn string, jf interface{}) (error) {
        file,err := os.Create(fn)
        if err != nil {
                return err
        }
        defer file.Close()
        byteJSON,err := json.MarshalIndent(jf,"","    ")
        if err != nil {
                return err
        }
        _,err = file.Write(byteJSON)
        if err != nil {
                return err
        }
        return nil
}
~~~


## YAMLについて
YAMLのパッケージが機能するためには、YAMLのキーに対応づくタグを、構造体へ記述しなければならない。タグの記述については、パッケージのドキュメントを参照していただきたい。

### YAML Decord (YAMLファイルから構造体へ)
JSONやXMLと同じようにコードを書ける。また、インタフェース型を利用することで、展開する構造体に依存しないコードになる。

~~~
package main
import (
        "github.com/go-yaml/yaml"
        "os"
)
func readYAML(fn string, yf interface{}) error {
        file, err := os.Open(fn)
        if err != nil {
                return err
        }
        defer file.Close()
        decoder := yaml.NewDecoder(file)
        err = decoder.Decode(yf)
        if err != nil {
                return err
        }
        return nil
}
~~~

### YAML Encode (構造体からYAMLファイルへ)
JSONやXMLと同じようにコードを書ける。

~~~
package main
import (
        "github.com/go-yaml/yaml"
        "os"
)
func writeYAML(fn string, yf interface{}) error {
        file, err := os.Create(fn)
        if err != nil {
                return err
        }
        defer file.Close()
        encoder := yaml.NewEncoder(file)
        defer encoder.Close()  
        err = encoder.Encode(yf)
        if err != nil {
                return err
        }
        return nil
}
~~~

### YAML Unmarshal (メモリ上のYAMLから構造体へ)
利用するパッケージ以外に、他と変わりが無い。

~~~
package main
import (
        "github.com/go-yaml/yaml"
        "os"
        "io"
)
func readYAML(fn string, yf interface{}) error {
        file, err := os.Open(fn)
        if err != nil {
                return err
        }
        defer file.Close()
        byteData,err := io.ReadAll(file)
        if err != nil {
                return err
        }
        err = yaml.Unmarshal(byteData,yf) //メモリ上のYAMLを構造体へ
        if err != nil {
                return err
        }
        return nil
}
~~~

### YAML Marshal　(構造体からメモリ上のYAMLへ)
インデントの設定メソッドが無い。YAMLは桁下げが必須なので、読みやすさの点で問題ない。

~~~
package main
import (
        "github.com/go-yaml/yaml"
        "os"
)
func writeYAML(fn string, yf interface{}) error {
        file, err := os.Create(fn)
        if err != nil {
                return err
        }
        defer file.Close()
        byteData,err := yaml.Marshal(yf) //メモリ上にYAMLを書き出し 
        if err != nil {
                return err
        }
        _,err = file.Write(byteData)
        if err != nil {
                return err
        }
        return nil
}
~~~

## まとめ
現在主要なデータ連携やAPIに利用されるXML,JSON,YAMLについて、Go言語の読書ついて調べて解ったことを、箇条書きにする。

* YAML,XMLのパッケージが機能するためには、構造体へタグの追加が必要。しかし、JSONは名前が一致していれば構造体へセットされる。
* Marshal/Unmarshalは、メモリ上のバイト型配列を対象した操作
* Encoder/Decoderは、ファイルI/Oなど、ストリーム処理を対象とした操作
* データ形式によってパッケージを変えるだけで、ほとんど変更なしで対応できそう
* インタフェース型を利用することで、メモリ上にデータを保持するための構造体に影響されないメソッドが書ける。


## 参考資料
* Config Files: INI, XML, JSON, YAML, TOML, https://www.barenakedcoder.com/blog/2020/03/config-files-ini-xml-json-yaml-toml/
* XMLパッケージ, https://pkg.go.dev/encoding/xml
* JSONパッケージ, https://pkg.go.dev/encoding/json
* YAMLパッケージ, https://pkg.go.dev/gopkg.in/yaml.v2