[Go]JSON文字列とデータオブジェクト間の変換

# 概要
Golangにおける標準パッケージ `json` を使った以下の操作について記述。
JSON文字列を構造体に変換( `json.Unmarshal` )
構造体をJSON文字列に変換( `json.Marshal` )

※Railsでいうところの `.to_json` , `JSON.parse` のお話。

以下、基本的な処理のサンプルコード。
https://go.dev/play/p/W5Of2mG79Mf

```sample.go
package main

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

// パースしたJSONを受け入れる構造体
type jsonBlock struct {
	StringKey string  `json:"string_key"`
	IntKey    int     `json:"int_key"`
	FloatKey  float64 `json:"float_key"`
}

func main() {
	// パース対象のJSONバイナリ
	jsonBlockBin := []byte(
		`{
			"string_key":"sample",
			"int_key": 1713420846688, 
			"float_key": 4.0
	 	}`,
	)

	// パース実行
	var jsonBlock jsonBlock
	if err := json.Unmarshal(jsonBlockBin, &jsonBlock); err != nil {
		log.Fatalf("json.Unmarshal() failed (jsonBlockBin:%s)", jsonBlockBin)
	}
	fmt.Printf("json.Unmarshal() succeeded (jsonBlock:%+v)\n", jsonBlock)

	// 構造体からJSONバイナリへ変換
	jsonBlockBin2, err := json.Marshal(jsonBlock)
	if err != nil {
		log.Fatalf("json.Marshal() failed (jsonBlock:%+v)", jsonBlock)
	}
	fmt.Printf("json.Marshal() succeeded (jsonBlockBin2:%s)\n", jsonBlockBin2)
}
```


**補足**
- JSONは、`json`パッケージでは string型 ではなく `[]byte`型で扱う
- 構造体は自前で定義する必要がある。
- `json.Unmarshal`, `json.Marshal` を実行する際、対象の構造体のフィールドは`exported`でないとエラーになる。
- 構造体のフィールドに`json`タグを付けることで、 JSONキー と データオブジェクトのフィールド 間を明確に紐づけられる(※)。

※jsonタグを付与せず、jsonパッケージにお任せすることもできるが、明示的に記述しておいた方がバグを産まないと思う。

# Marshal, Unmarshal処理の明示化
構造体に自前で `UnmarshalJSON`, `MarhsalJSON`メソッドを定義しておくと、`json.Unmarshal`, `json.Marshal`実行時にそちらを実行してくれる。

以下のサンプルコードは、`json`パッケージでの `Unmarshal` では、JSONでのUNIX時間ミリ秒をいい感じにtime.Timeとしてパースすることができない・・・ということで対処した例。
https://go.dev/play/p/t8GzDK5lB7X

```sample.go
package main

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

// jsonBlock パースしたJSONを受け入れる構造体
type jsonBlock struct {
	StringKey string
	TimeKey   time.Time // JSTの日時。JSONではUNIX時間(ミリ秒)の整数値で扱われるもの
	FloatKey  float64
}

// UnMarhsalJSON JSONバイナリからjsonBlockへ変換
// UNIX時間(秒)はjsonパッケージ側でいい感じにパースしてtime.Timeのフィールドにセットできないため、int64のフィールドを持つ構造体でパースさせる
func (j *jsonBlock) UnmarshalJSON(jsonBlockBin []byte) error {
	var tmpJsonBlock struct {
		StringKey string  `json:"string_key"`
		IntKey    int64   `json:"int_key"`
		FloatKey  float64 `json:"float_key"`
	}
	if err := json.Unmarshal(jsonBlockBin, &tmpJsonBlock); err != nil {
		return fmt.Errorf("json.UnMarshal failed (jsonBlockBin:%+v, err:%+v)", jsonBlockBin, err)
	}

	j.StringKey = tmpJsonBlock.StringKey
	j.TimeKey = time.Unix(0, tmpJsonBlock.IntKey*1000000).In(time.FixedZone("Asia/Tokyo", 9*60*60))
	j.FloatKey = tmpJsonBlock.FloatKey

	return nil
}

// MarshalJSON jsonBlockからJSONバイナリへ変換
// time.Time型はjsonパッケージ側でいい感じにUNIX時間(秒)へ変換できないため、自身で変換してint64のフィールドを持つ構造体にしてからJSON化する
func (j *jsonBlock) MarshalJSON() ([]byte, error) {
	var tmpJsonBlock struct {
		StringKey string  `json:"string_key"`
		IntKey    int64   `json:"int_key"`
		FloatKey  float64 `json:"float_key"`
	}
	tmpJsonBlock.StringKey = j.StringKey
	tmpJsonBlock.IntKey = j.TimeKey.UnixNano() / 1000000
	tmpJsonBlock.FloatKey = j.FloatKey

	jsonBlockBin, err := json.Marshal(tmpJsonBlock)
	if err != nil {
		return nil, fmt.Errorf("json.Marshal() failed (tmpJsonBlock:%+v, err:%+v)", tmpJsonBlock, err)
	}
	return jsonBlockBin, nil
}

func main() {
	// パース対象のJSONバイナリ
	jsonBlockBin := []byte(
		`{
			"string_key":"sample",
			"int_key": 1713420846688, 
			"float_key": 4.0
	 	}`,
	)

	// JSONバイナリから構造体へ変換
	var jsonBlock jsonBlock
	if err := json.Unmarshal(jsonBlockBin, &jsonBlock); err != nil {
		log.Fatalf("json.Unmarshal() failed (jsonBlockBin:%s, err:%+v)", jsonBlockBin, err)
	}
	fmt.Printf("json.Unmarshal() succeeded (jsonBlock:%+v)\n", jsonBlock)

	// 構造体からJSONバイナリへ変換
	jsonBlockBin2, err := json.Marshal(&jsonBlock)
	if err != nil {
		log.Fatalf("json.Marshal() failed (jsonBlock:%+v, err:%+v)", jsonBlock, err)
	}
	fmt.Printf("json.Marshal() succeeded (jsonBlockBin2:%s)\n", jsonBlockBin2)
}
```

**補足**
- サンプルコードの通り、`json` パッケージに直接的に変換処理を任せている部分だけ、その構造体のフィールドが`exported`であれば良い模様。
- 自分だけかもしれないが、`MarshalJSON, UnmarshalJSON`の「JSON」をつけ忘れて、自前の処理全然通ってくんねぇ・・・とハマってたので名前に注意。

# 参考
https://zenn.dev/hsaki/articles/go-time-cheatsheet#unix%E6%99%82%E9%96%93%E3%81%8B%E3%82%89%E3%81%AE%E5%A4%89%E6%8F%9B

https://scrapbox.io/nwtgck/Go%E8%A8%80%E8%AA%9E%E3%81%A7Unix%E6%99%82%E9%96%93%E3%81%B8%E3%81%AE%E7%9B%B8%E4%BA%92%E5%A4%89%E6%8F%9B%EF%BC%88%E3%83%9F%E3%83%AA%E7%A7%92%EF%BC%89

https://zenn.dev/satumahayato010/articles/ae2484d53c6a11

https://oohira.github.io/gobyexample-jp/epoch.html