概要
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」をつけ忘れて、自前の処理全然通ってくんねぇ・・・とハマってたので名前に注意。
参考