LoginSignup
0
0

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

Last updated at Posted at 2024-04-18

概要

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

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0