json.Unmarshalする際に時刻のフォーマットを柔軟に変更できるようにする #Go

この記事は [Go Advent Calendar 2017](https://qiita.com/advent-calendar/2017/go) ５日目の記事です。

## はじめに

みなさんはじめまして、@cia_ranaと申します。

今回は `json.Unmarshal` する際に時刻のフォーマットを自由に変更できるようにするためのライブラリ[`tson`（ツォン）](https://github.com/cia-rana/tson)を開発したお話です。

:warning: `tson`は **†黒魔術†** を使用しています。用法用量を守り正しくお使いください。

## この記事で使う構造体や変数

ここでは本記事で使用する構造体や変数を定義します。`hoge`とか`huga`とかでは頭に入ってきづらいと思うので、少し意味を持たせた内容となっています。

構造体`Person`は、`Person`の特徴`Feature`と、`Person`が作成された日付`CreatedAt`を持ちます。

```go
type Person struct {
    Feature   Feature    `json:"feature"`
    CreatedAt *time.Time `json:"created_at"`
}
```

構造体`Feature`は、`Person`が持つ特徴の内容を表しています。

```go
type Feature struct {
    Name  string     `json:"name"`
    Birth *time.Time `json:"birth"`
}
```

変数`jsonString`は、構造体`Person`のオブジェクトにアンマーシャル[^1]する対象となるJSON文字列です。特に時刻のフィールド（`birth` と `created_at`）が今回のキモとなるので覚えておいてください！

```go
var jsonString = `
{
    "feature": {
        "name": "cia_rana",
        "birth": "1987-10-08"
    },
    "created_at": "2017-12-05"
}
`
```

[^1]: 「パース」、「デシリアライズ」ともいう

## まずはライブラリを使ってみよう

`tson`を使ったJSON文字列から構造体のオブジェクトへのアンマーシャルは、標準ライブラリ`encoding/json`の関数`json.Unmarshal`と同じやり方、つまり関数`tson.Unmarshal`でできます。

ただ通常の`json.Unmarshal`と違うのは、`tson.Unmarshal`の手前で関数`tson.SetLayout`を呼んでいるところです。`tson.SetLayout`は時刻のフォーマットを変更する関数で、この関数を呼んだ後に`tson.Unmarshal`する際は変更したフォーマットが使用されます。

```go
var person Person

tson.SetLayout(`2006-01-02`)

err := tson.Unmarshal([]byte(jsonString), &person)
if err != nil {
    fmt.Println("error:", err)
} else {
    fmt.Println(person)
}
// => {{cia_rana 1987-10-08 00:00:00 +0000 UTC} 2017-12-05 00:00:00 +0000 UTC}
```

きちんとアンマーシャルできていますね！

一体この処理の裏側では何が行われているのでしょう？

## この記事を書こうと思ったきっかけ
### ところで・・・
GoでJSON文字列を構造体のオブジェクトにアンマーシャルしたとき、こんな経験した人いますよね？

```go
var person Person

err := json.Unmarshal([]byte(jsonString), &person)
if err != nil {
    fmt.Println("error:", err)
} else {
    fmt.Println(person)
}
// => error: parsing time ""1988-10-10"" as ""2006-01-02T15:04:05Z07:00"": cannot parse """ as "T"
```

エラー！！？

そう、GoでJSON中の時刻を`*time.Time`にマッピングするとき、その時刻は[Goがもともと用意しているフォーマット](https://github.com/golang/go/blob/release-branch.go1.8/src/time/time.go#L972)で記述されていなければきちんとマッピングできません。
もうちょっと融通が効いても良いと思うのですが、こればかりはGoの仕組み上どうしようもないのです。

ちなみに時刻の文字列を`time.Time`型のオブジェクトに変換する場合、時刻のフォーマットを明示的に指定できるのですが、Goはその指定の仕方が特殊で、`YYYY-MM-DD`のように指定するのではなく、[プログラマが読みやすいフォーマットで指定できる](https://qiita.com/unbabel/items/c8782420391c108e3cac#golang%E3%81%AE%E5%A0%B4%E5%90%88)ようになっています。それゆえ細かい数字を覚えなくてはいけません・・・。

### 解決策

さて、エラーが出ないようにきちんとアンマーシャルするためには、普通は構造体内の`*time.Time`型を[`UnmarshalJSON`を備えたオリジナルのTime型に変更します](https://qiita.com/taizo/items/2c3a338f1aeea86ce9e2)よね？

ただ、いちいち構造体を書き換えて`UnmarshalJSON`を実装するのは大変だし、ライブラリ内の構造体を利用したい場合その構造体を容易に変更できない場合すらあります。

そこで、プログラム処理中に元からある構造体を基に「自由に時刻のフォーマットを変更できる新しい構造体を動的に生成する」という新しいアプローチを提案します。

このアプローチによって、"[まずはライブラリを使ってみよう](#%E3%81%BE%E3%81%9A%E3%81%AF%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%81%BF%E3%82%88%E3%81%86)"の章で見たように、用意された関数に構造体のオブジェクトを渡すだけで、あらかじめ設定した時刻のフォーマットに基づいてJSON文字列内の時刻を`*time.Time`にうまいことマッピングしながらアンマーシャリングを行うことができるようになります。

以降はこのアプローチをどのように実現したのか、その中身を紐解いていきます。

## さぁ、黒魔術をはじめよう

時刻のフォーマットを考慮しながらJSON文字列を`Person`オブジェクトにアンマーシャルする手順は次の通りです。

1. `Person`の型情報を取得する
2. `Person`型情報内の`*time.Time`型を`tson`側で用意したオリジナルの`Time`型に置換した新たな型を生成する
3. 新たな型のオブジェクトを生成する
4. JSON文字列を新たな型のオブジェクトにアンマーシャルする
5. 新たな型のオブジェクトを再びJSON文字列に変換する
6. 再変換したJSON文字列を`Person`のオブジェクトにアンマーシャルする

![json_unmarshal.png](https://qiita-image-store.s3.amazonaws.com/0/72064/c0cfbc28-ee79-b074-069c-2bcfa963099f.png)

`reflect`を多用した黒魔術の香りがぷんぷんしますね:blush:
それでは、黒魔術の手順をそれぞれ見ていきましょう。

### 1. `Person`の型情報を取得する
まずはベースとなる`Person`の型情報を取得します。
型情報を取得するには通常は関数`reflect.TypeOf`を使います。ですが、`Person`はポインタ型で入ってくる前提なので（`tson.Unmarshal([]byte(jsonString), &person)`の`&person`の部分より）、一旦ポインタが指す値を取得した後その型情報を取得します。ポインタが指す値を取得するには関数`reflect.Elem`を使います。あとはその戻り値の関数`Type`を呼んでやれば良いです。

```go
var v {}interface := &person // `Person`はポインタで入ってくる
rv := reflect.ValueOf(v)     // personの型情報を取得する。personの型情報は`reflect.Ptr`である
rt := rv.Elem().Type()       // `reflect.Ptr`が指す値の型情報を取得する
```

これで`Person`の型情報が取得できました。

### 2. `Person`型情報内の`*time.Time`型を`tson`側で用意したオリジナルの`Time`型に置換した新たな型を生成する

ここでは、先ほど取得した`Person`の型情報に含まれる`*time.Time`をオリジナルの`Time`型（のポインタ）に置換していきます。ですが、その前にオリジナルの`Time`型について説明します。

オリジナルの`Time`型（`tson.Time`と呼ぶ）は、関数`tson.SetLayout`で指定した時刻のフォーマットに従ってJSON文字列内の時刻を`tson.Time`にマッピングするという特徴を持っています。`tson.SetLayout`で指定した時刻のフォーマットは`tson`側のプライベートな変数`format`に格納されています。また、時刻のフォーマットはスレッドセーフに指定できるようにしています。それ以外はよく見る`time.Time`のラッパーの形ですね。

```go
var format = struct {
    layout string
    mutex  *sync.Mutex
}{
    time.RFC3339,
    new(sync.Mutex),
}

func SetLayout(layout string) {
    format.mutex.Lock()
    format.layout = layout
    format.mutex.Unlock()
}

type Time struct {
    time.Time
}

func (t *Time) UnmarshalJSON(data []byte) error {
    if string(data) == "null" {
        return nil
    }
    
    tm, err := time.Parse(`"`+format.layout+`"`, string(data))
    t.Time = tm
    return err
}
```

`tson.Time`の型情報は頻繁に使うため、`tson`側のプライベートな変数`rtt`であらかじめ持っておきます。

```go
var rtt = reflect.TypeOf(&Time{})
```

さて、これで準備が整いました。
黒魔術をはじめましょう。

・・・と、カッコつけましたがやることはいたって単純で、`*time.Time`を置換するだけです（笑）
ここでは便宜的に`Person`の型情報を`rt`としておきます。

まずは、新しく生成する構造体のフィールドをまとめるスライスを定義します。スライスの型は`[]reflect.StructField`です。フィールドの数は`rt`のフィールドの数と同じであり、スライスの長さが分かっているので、メモリアロケーションの回数を減らす意味も込めてスライスの定義は次のように行います。

```go
rs := make([]reflect.StructField, rt.NumField())
```

次に`rt`のフィールドにアクセスします。型情報の種別が`Struct`である`rt`のフィールドへのアクセスは、インデックスを用いて次のようにアクセスします。

```go
f := rt.Field(i)
```

そして、ここから構造体のフィールドを置き換える作業になります。

戻り値`f`のフィールド`Type`が`*time.Time`である場合、それを`tson.Time`の型情報で置き換えます。置き換えはただ`tson.Time`の型情報を代入するだけで良いです。

```go
f.Type = rtt
```

一方、`f.Type`が`*time.Time`でない場合、一見何も置き換える必要がないと思いますよね？ですが、`f.Type`の種別が`Array`や`Slice`、`Struct`などの場合、それらの構成要素の中で`*time.Time`が使われているかもしれません。
例えば`f`がスライス`[]*time.Time`の場合`f.Type`は`Slice`ですが、このスライスの要素の型は`*time.Time`であり、置き換えの対象になります。また、`f`の型の構成要素が直接`*time.Time`を持たない場合でも、構成要素の構成要素が`*time.Time`である可能性があります。つまり、再帰的に`f`の構成要素にアクセスして置換していきます。
構成要素へのアクセスおよび置換された構成要素を持つ新しい型の生成は、それぞれの型の種別に合わせて行わなければいけません。

最後に、新しく生成する構造体のフィールドをまとめておいたスライスを、関数`reflect.StructOf(rs)`によって新しく生成する構造体の型情報へ変換します。

以上をまとめると、次の関数`newStruct`のようになります。

```go
func newStruct(rt reflect.Type) reflect.Type {
    rs := make([]reflect.StructField, rt.NumField())
    
    for i := 0; i < rt.NumField(); i++ {
        f := rt.Field(i)
        
        if f.Type.String() == "*time.Time" {
            f.Type = rtt
        } else {
            switch f.Type.Kind() {
            case reflect.Array:
                f.Type = reflect.ArrayOf(f.Type.Len(), newStruct(f.Type.Elem()))
            case reflect.Chan:
                f.Type = reflect.ChanOf(f.Type.ChanDir(), newStruct(f.Type.Elem()))
            case reflect.Func:
                ins := make([]reflect.Type, f.Type.NumIn())
                outs := make([]reflect.Type, f.Type.NumOut())
                for i := 0; i < f.Type.NumIn(); i++ {
                    ins[i] = newStruct(f.Type.In(i))
                }
                for i := 0; i < f.Type.NumOut(); i++ {
                    outs[i] = newStruct(f.Type.Out(i))
                }
                f.Type = reflect.FuncOf(ins, outs, f.Type.IsVariadic())
            case reflect.Interface:
                // TODO
            case reflect.Map:
                f.Type = reflect.MapOf(newStruct(f.Type.Key()), newStruct(f.Type.Elem()))
            case reflect.Ptr:
                f.Type = reflect.PtrTo(newStruct(f.Type.Elem()))
            case reflect.Slice:
                f.Type = reflect.SliceOf(newStruct(f.Type.Elem()))
            case reflect.Struct:
                f.Type = newStruct(f.Type)
            case reflect.UnsafePointer:
                // TODO
            }
        }
        
        rs[i] = f
    }
    
    return reflect.StructOf(rs)
}
```

`//TODO`としているところはまだ未実装のところですが、今回のデモでは必要ないので省略しました。

この記事の山場は乗り越えたので、ここからはサクサクと説明していきたいと思います！

### 3. 新たな型のオブジェクトを生成する

まずは先ほど新しく生成した構造体の型情報を変数`rt`に格納しておきます。この`rt`は2.の`rt`とは別物です。

```go
rt, err := NewStruct(v)
```

型情報`rt`からオブジェクトを生成するには次のようにすれば良いです。新たに生成された`vi`の型は[`interface{}`です](https://golang.org/pkg/reflect/#Value.Interface)。

```go
vi := reflect.New(rt).Interface()
```

### 4. JSON文字列を新たな型のオブジェクトにアンマーシャルする

生成したオブジェクトにJSON文字列をいつもやってる通りアンマーシャルします。自分で指定した時刻のフォーマットに基づいてアンマーシャルするため、そのフォーマットの指定の仕方が間違っていなければきちんとアンマーシャルされるはずです。

```go
err := json.Unmarshal(jsonBytes, &vi)
```

### 5. 新たな型のオブジェクトを再びJSON文字列に変換する

え、なんでここでJSON文字列を生成するの？と思われるかもしれません。これは最終的に`Person`のオブジェクトにJSON文字列をきちんとアンマーシャルするために行っています。元のJSON文字列中の時刻のフィールドがGo標準の時刻フォーマットに変換された新たなJSON文字列が生成されるので、何も弄っていない`Person`オブジェクトにJSON文字列をきちんとアンマーシャルできるようになるのです。

```go
data, err := json.Marshal(vi)
```

### 6. 再変換したJSON文字列を`Person`のオブジェクトにアンマーシャルする

生成したJSON文字列を`Person`文字列にアンマーシャルします。5.で述べた通りきちんとアンマーシャルできるはずです。

```go
err := json.Unmarshal(data, v)
```

## おわりに
ここまで、黒魔術の内容についてざっくりと見てきました。文章量はそこそこありましたが、やってきたことはただ__型を置き換えた新たな構造体を動的に作る__ということだけでした。ただ、`reflect`を多用しており速度面やコンパイル時の型チェックが不十分な場合があるので、冒頭でも述べましたが用法用量を守り正しくお使いください。

というネタでした。

## 参考文献
- [みんなのGo言語 【現場で使える実践テクニック】](http://gihyo.jp/book/2016/978-4-7741-8392-3)
- [Package reflect - golang.org](https://golang.org/pkg/reflect/)
- [Go 言語の reflect チートシート - Qiita](https://qiita.com/nirasan/items/b6b89f8c61c35b563e8c)