グローバルナビゲーションへ

本文へ

フッターへ

お役立ち情報Blog



【Go】jsonパッケージのMarshalとUnmarshalはなんとなくわかったが、Encode, Decodeって何?

普段のコードでJSON関係の変換をする際にはjsonパッケージのMarshlUnmarshalを使っているのですが、jsonパッケージの中に同じようにJSONを変換するEncoder型のメソッドEncode及びDecoder型のメソッドDecodeなるものを見つけ、いったいMarhsalUnmarshalと何が違うのかと疑問に思い、調べてみました。

jsonパッケージへのリンク

まずMarhsalとUnmarshalの使い方

以下はMarhsalUnmarshalの利用例です。

b := []byte(`{"Name":"Alice","Age":6,"Parents":["Bob","Chris"]}`)

var m map[string]any
if err := json.Unmarshal(b, &m); err != nil {
    fmt.Println("error")
}
fmt.Println(m) // map[Age:6 Name:Alice Parents:[Bob Chris]]

bb, err := json.Marshal(m)
if err != nil {
    fmt.Println("error")
}

fmt.Println(string(bb)) // {"Age":6,"Name":"Alice","Parents":["Bob","Chris"]}

上のコードではJSON形式なデータのバイトスライスをマップに変換し、再度JSON形式のバイトスライスのデータに変換しています。

EncodeとDecodeで同じ処理を書いてみる

EncodeDecodeを使って上と同じ処理を実装してみます。

b := []byte(`{"Name":"Alice","Age":6,"Parents":["Bob","Chris"]}`)
buf := bytes.NewReader(b)
dec := json.NewDecoder(buf)

var m map[string]any
if err := dec.Decode(&m); err != nil {
    fmt.Println("error")
}
fmt.Println(m) // map[Age:6 Name:Alice Parents:[Bob Chris]]

var builder strings.Builder
enc := json.NewEncoder(&builder)

if err := enc.Encode(m); err != nil {
    fmt.Println("error")
}

fmt.Println(builder.String()) // {"Age":6,"Name":"Alice","Parents":["Bob","Chris"]}

Decoderio.Readerインターフェイスを満たす型を渡してDecodeをし、Encoderにはio.Writerインターフェイスを満たす型を渡してEncodeをしています。余計な処理が増えて先ほどのMarshalUnmarshalを使って書いたコードよりわかりづらくなったように思います。こういった処理には素直にMarshalUnmarshalを使った方が良さそうです。

それではDecode及びEncodeの使いどころはいったいどこでしょうか。

EncodeとDecodeの使いどころ

EncodeとDecodeの使いどころは、ストリーミングな処理を書きたい場合です。

const jsonStream = `
{"Name":"Alice","Age":6,"Parents":["Bob","Chris"]}
{"Name":"Dean","Age":20,"Parents":["Erick","Fern"]}
{"Name":"Gerald","Age":19,"Parents":["Harry","Irene"]}
{"Name":"Jane","Age":11,"Parents":["Kelvin","Lucy"]}
`

reader := strings.NewReader(jsonStream)
dec := json.NewDecoder(reader)

var builder strings.Builder
enc := json.NewEncoder(&builder)

for {
    var m map[string]any
    if err := dec.Decode(&m); err == io.EOF {
        break
    } else if err != nil {
        fmt.Println(err)
        break
    }

    delete(m, "Parents")

    if err := enc.Encode(m); err != nil {
        fmt.Println("error")
    }
}

fmt.Println(builder.String()) // 以下のように出力される
/*
    {"Age":6,"Name":"Alice"}
    {"Age":20,"Name":"Dean"}
    {"Age":19,"Name":"Gerald"}
    {"Age":11,"Name":"Jane"}

*/

上のコードではJSON形式のデータからJSONオブジェクトを一つ読み込んでmap[string]anyに変換し、そのマップからParentsキーを削除し、再度JSON形式のデータに変換して書き込む、という処理をデータの全てのJSONオブジェクトに行っています。
大量のJSONのデータが書かれているファイルやリクエストをストリーミング処理したい場合に活躍してくれそうです。

さいごに

今回調べてみて、ストリーミング処理を行いたい場合はDecoderDecode及びEncoderEncodeメソッドを使い、そうでない場合はMarshal及びUnmarshalを使っていけば良さそうだということがわかりました。
正直ストリーミング処理についてはまだ理解が浅いので調べを進めるのに苦労しましたが、これから理解を深めて今回調べたDecodeEncodeなどを使ってバリバリにストリーミング処理を書いていきたいです。

この記事を書いた人

wanderlust
wanderlust事業開発部 web application engineer
これまで農業、士業と経験し、まったく異業種のエンジニアとしてアーティスに入社。
現在は事業開発部でバックエンドエンジニアとして仕事に従事。可読性の高いコードが書けるよう日々勉強中。趣味は一人旅。
この記事のカテゴリ

FOLLOW US

最新の情報をお届けします