Golangで構造体を使ったJSON操作で出来ることを調べてみた

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

西田@大阪です。

Golangでできる構造体を使ったJSON操作を調べてみました。

struct tags

Golangでは structタグでJSONの処理をある程度制御することができます。

記述例

type Sample struct {
    // json:<マッピングするJSONオブジェクトのフィールド名>,<オプション> という形式で記述します
    FieldName string `json:"field_name,string"`
}

用意されているオプションは以下の通りです。

タグ Marshal(JSON出力)時 Unmarshal(JSONパース)時
omitempty 0値(空文字、0、nil等)であればフィールドを出力しない 0値であれば無視される json:"field,omitempty"
- 出力しない 無視される json:"-"
string 出力時に Quote される Quoteされていても型に合わせて変換する。Quoteされてないとエラー json:"field,string"
type JSONSample struct {
    Field string `json:"field"`
    Omit string `json:"-"`
    OmitEmpty string `json:"omit_empty,omitempty"`
    Num int `json:"num,string"`
}

func main() {
    sample := JSONSample{
        Field:     "field",
        Omit:      "omit",
        OmitEmpty: "",
        Num:       1,
    }

    bytes, _ := json.Marshal(&sample)

    fmt.Println(string(bytes))
    /* -- output

    {
        "field": "field",
        "num":"1"
    }

    - omitは省略されています
    - omit_emptyは値がないので、JSONではフィールドごと省略されてます
    - numは Golangではint型ですが、JSONではstructタグでstringが指定されているのでQuoteされて出力されています
    */

    j := `
    {
      "field": "field",
      "omit": "omit",
      "omit_empty": "a",
      "num": "123"
    }
    `
    var decoded JSONSample
    json.Unmarshal([]byte(j), &decoded)

    fmt.Printf("%v\n", decoded)
    /* -- output
        {field  a 123}

        - omitは値があってもけされています
        - emit_emptyは値があるので、構造体に値として設定されています
        - numはJSONでは文字列ですが、structタグで設定 string を設定されているので構造体の型であるintに変換されてます
    */
}

カスタム

structタグのみでできない処理は MarshalJSON UnmarshalJSON 関数を定義しカスタムすることが可能です。

例えば、time.Time 型は RFC3339(2006-01-02T15:04:05Z07:00) しか time.Time型に変換できないですが、MarshalJSON UnmarshalJSONメソッドを定義することによって、別の日付のフォーマットでも取り扱うことが可能です。

下記は 2017/09/05 のようなフォーマットで日付を扱いたい場合の例です。

// 新しい構造体を宣言します。元の構造体と同じように扱いたいので embedded してます
type MyTime struct {
    *time.Time
}

// Unmarshal時の動作を定義します
func (mt *MyTime) UnmarshalJSON(data []byte) error {
    // 要素がそのまま渡ってくるので "(ダブルクォート)でQuoteされてます
    t, err := time.Parse("\"2006/01/02\"", string(data))
    *mt = MyTime{&t}
    return err
}

// Marshal時の動作を定義します
func (mt MyTime) MarshalJSON() ([]byte, error) {
    return json.Marshal(mt.Format("2006/01/02"))
}
type JSONSample struct {
    TimeAt MyTime `json:"time_at"`
}

func main() {
    j := `
    {
        "time_at":"2017/09/12"
    }
    `

    var decoded JSONSample
    json.Unmarshal([]byte(j), &decoded)
    fmt.Printf("%v\n", decoded)
    /* -- output
        {2017-09-12 00:00:00 +0000 UTC}
    */

    j2, _ := json.Marshal(decoded)
    fmt.Printf("%v", string(j2))
    /* -- output
        {"time_at":"2017/09/12"}
    */
}

参考