技術ブログ

構造体からJSONを生成する際に任意の形式で時刻文字列を出力する

author: yterajimadate: 2016-03-22tags: golang

Go 言語で API を作成する場合のレスポンスに含む時刻の話です。 Golang で構造体を用意して json パッケージを使って JSON を生成すると次のようなコードと結果になります。

package main

import (
  "encoding/json"
  "fmt"
  "time"
)

func main() {
  data := data{
    Data:      "something string",
    CreatedAt: time.Now(),
  }
  res, err := json.Marshal(data)
  if err != nil {
    fmt.Println(err)
  }

  fmt.Println(string(res))
  // Output: {"data":"something string","created_at":"2016-03-22T00:39:22.010718997+09:00"}
}

type data struct {
  Data      string    `json:"data"`
  CreatedAt time.Time `json:"created_at"`
}

上記のコードではレスポンス用データを構造体 data で作成し, json.Marshal で JSON データ ([]byte) に変換して出力しています。data 構造体には作成時間を表す CreatedAt が含まれます。CreatedAttime.Time 型です。

この time.Time 型を json.Marshal で変換した場合に出力される形式は W3C Date Time Format の YYYY-MM-DDThh:mm:ss.sTZD 形式になります。サンプルの出力では created_at の秒数に小数点以下の値が含まれています。

任意の時刻文字列に変換したい

API の仕様として time.Time 型を変換した場合には 2016-03-22T00:39:22+09:00 のように秒数に小数点以下の値が含まれない形式が好ましいという等々の事情がありました。そこで time.Time を JSON に変換する場合に任意の時刻文字列に変換する処理をどうにかして実現する必要がありました。

調査してみたところ次の Qiita 記事に対応方法がありました。

参考: データをJSONに変換するときに任意のフォーマットを設定する - Qiita

簡単にまとめると次の対応で実現できるようです。

  • 任意の構造体を定義
  • MarshalJSON 関数をメソッドとして追加
  • time.Time で定義していた部分を追加した構造体に置き換え

実際にやってみる

記事を参考にコードを修正してみました。myTime 構造体を追加して MarshalJSON メソッドを追加しています。

package main

import (
  "encoding/json"
  "fmt"
  "time"
)

func main() {
  data := data{
    Data:      "something string",
    CreatedAt: myTime{Time: time.Now()},
  }
  res, err := json.Marshal(data)
  if err != nil {
    fmt.Println(err)
  }

  fmt.Println(string(res))
  // Output: {"data":"something string","created_at":"2016-03-22T01:15:56+09:00"}
}

type data struct {
  Data      string `json:"data"`
  CreatedAt myTime `json:"created_at"`
}

// 任意の形式で時刻文字列を出力するための構造体
type myTime struct {
  time.Time
}

// JSON に変換される際に呼び出されるメソッド
func (t myTime) MarshalJSON() ([]byte, error) {
  return []byte(`"` + t.Time.Format("2006-01-02T15:04:05-07:00") + `"`), nil
}

出力結果は 2016-03-22T01:15:56+09:00 になったので欲しい文字列が得られました。

詳細を調査

type Marshaler

Marshaler is the interface implemented by objects that can marshal themselves into valid JSON.

訳: Marshaler は有効な JSON に自身を変換できるオブジェクトによって実装されるインターフェイスです。

Marshaler インターフェイスに対応すると, 構造体から JSON に変換する際に MarshalJSON メソッドが呼び出され変換されるようです。

これに対して Unmarshaler インターフェイスも用意されていて JSON から構造体に json.Unmarshal で変換される場合に使用される UnmarshalJSON メソッドが定義できるようです。

Go 言語の time パッケージを確認すると該当部分に処理とコメントがありました。

go/time.go at master · golang/go

気になるコメント

https://github.com/golang/go/blob/master/src/time/time.go#L916-L918

// TODO(rsc): Remove GobEncoder, GobDecoder, MarshalJSON, UnmarshalJSON in Go 2.
// The same semantics will be provided by the generic MarshalBinary, MarshalText,
// UnmarshalBinary, UnmarshalText.

v2 からは追加するメソッドが変わるらしい。