Goで、json.UnmarshalとMarshalとエンコード

Golangの、UnmarshalとMarshalについて解説してみたいと思います。 ネットワーク越しで、取ってきたデータをGoの構造体に変換したり、Goの構造体からJSONに変換してデータを送信したりと使用する機会も多いと思うので、是非参考にしていただければと思います。

Unmarshal

Unmarshalは、ネットワーク越しに送信されたデータをGoの構造体に変換します。

func Unmarshal(data []byte, v any) error

Unmarshalは、JSON形式で受け取った値を指定した構造体に格納することができます。第1引数にJSON形式のデータを、第2引数に格納したい構造体を指定します。 第2引数の値がnilもしくはポインタではない場合、InvalidUnmarshalErrorを返します。

package main

import (
    "encoding/json"
    "fmt"
)

type Book struct {
    Title     string
    Author    string
    Publisher string
}

func main() {
    b := []byte(`{"title": "リーダブルコード", "author": "Trevor Foucher", "Publisher": "OREILLY"}`)
    var book Book
    if err := json.Unmarshal(b, &book); err != nil {
        fmt.Println(err)
    }
    fmt.Println(book.Title, book.Author, book.Publisher)
}

実行してみます。

$ docker-compose up
go_1  | running...
go_1  | リーダブルコード Trevor Foucher OREILLY

ネットワークで入ってきた、byteのスライスをBook構造体のキーをみて変換してくれるのが、Unmarshalとなります。

Marshal

Goの構造体に格納したデータを、JSON形式に変換してネットワーク越しに送信したい場合に、Marshalを使用します。

func Marshal(v any) ([]byte, error)

Marshalは、引数の値をJSON形式にエンコーディングして返します。

package main

import (
    "encoding/json"
    "fmt"
)

type Book struct {
    Title     string
    Author    string
    Publisher string
}

func main() {
    b := []byte(`{"title": "リーダブルコード", "author": "Trevor Foucher", "Publisher": "OREILLY"}`)
    var book Book
 
    v, err := json.Marshal(book)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(string(v))
}

実行してみます。

$ docker-compose up
go_1  | running...
go_1  | {"Title":"リーダブルコード","Author":"Trevor Foucher","Publisher":"OREILLY"}

JSON形式の値が返って来ていることが確認できます。

Marshalを使ってJSON形式で出力をしましたが、キーの名前が大文字になっています。JSON形式でデータを扱う場合、大文字になることはあまり無いため小文字にしてみましょう。小文字で指定をするには、構造体にタグを付けることで対応できます。 Marshalするときには、どのような名前でエンコードするかを指定することができます。

type Book struct {
    Title     string `json:"title"`
    Author    string `json:"author"`
    Publisher string `json:"publisher"`
}

再度実行してみます。

$ docker-compose up
go_1  | running...
go_1  | {"title":"リーダブルコード","author":"Trevor Foucher","publisher":"OREILLY"}

構造体の頭文字は大文字ですが、タグをつけることでキーが小文字になっていることが確認できます。

データを隠す

このデータは表示させたくないというケースがあると思います。そのような場合は、JSONのタグにハイフンを指定することで、データを隠すことができます。

type Book struct {
    Title     string `json:"title"`
    Author    string `json:"-"`
    Publisher string `json:"publisher"`
}

実行してみます。

$ docker-compose up
go_1  | running...
go_1  | {"title":"リーダブルコード","publisher":"OREILLY"}

ハイフンを指定した、Authorが表示されていないのが確認できると思います。 著者名を隠すってどんなケースだよ!ですよね笑、題材が悪かったですね笑 パスワードとかkeyとかで使えるのかなと思います!

Marshalを独自カスタマイズ

Marshalを拡張させて、独自に処理を加えてカスタマイズすることもできます。便利ですね! MarshalJSONの形で記述しないとカスタマイズすることができないです。Marshalが呼ばれた時に自動的にMarshalJSONメソッドが呼ばれる様になります。

package main

import (
    "encoding/json"
    "fmt"
)

type Book struct {
    Title     string `json:"title"`
    Author    string `json:"-"`
    Publisher string `json:"publisher"`
}

func (b Book) MarshalJSON() ([]byte, error) {
    v, err := json.Marshal(&struct {
        Publisher string
    }{
        Publisher: b.Publisher + "Japan",
    })
    return v, err
}

func main() {
    b := []byte(`{"title": "リーダブルコード", "author": "Trevor Foucher", "Publisher": "OREILLY"}`)
    var book Book
    if err := json.Unmarshal(b, &book); err != nil {
        fmt.Println(err)
    }
    fmt.Println(book.Title, book.Author, book.Publisher)

    v, err := json.Marshal(book)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(string(v))
}

実行してみます。

$ docker-compose up
go_1  | running...
go_1  | {"Publisher":"OREILLY Japan"}

OREILLYから、OREILLY Japanに変わってると思います。

Unmarshalを独自カスタマイズ

Marshalと同様にUnmarshalもUnmarshalJSONを使用すれば、カスタマイズできます。

package main

import (
    "encoding/json"
    "fmt"
)

type Book struct {
    Title     string `json:"title"`
    Author    string `json:"author"`
    Publisher string `json:"publisher"`
}

func (b *Book) UnmarshalJSON(byte []byte) error {
    type Book2 struct {
        Title string
    }
    var b2 Book2
    err := json.Unmarshal(byte, &b2)
    if err != nil {
        fmt.Println(err)
    }
    b.Title = b2.Title + "-より良いコードを書くためのシンプルで実践的なテクニック"
    return err
}

func main() {
    b := []byte(`{"title": "リーダブルコード", "author": "Trevor Foucher", "Publisher": "OREILLY"}`)
    var book Book
    if err := json.Unmarshal(b, &book); err != nil {
        fmt.Println(err)
    }
    fmt.Println(book.Title, book.Author, book.Publisher)

    v, err := json.Marshal(book)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(string(v))
}

実行してみます。

$ docker-compose up
go_1  | running...
go_1  | {"title":"リーダブルコード-より良いコードを書くためのシンプルで実践的なテクニック",

Marshalと同様しっかりカスタマイズできていますね! いかがだったでしょうか、UnmarshalとMarshalは、使用頻度も高いと思いますので、少しでも理解の助けになればと思います!