Marshalling & Unmarshalling JSON - Golang learning step 2-13
- 公開日
- カテゴリ:GoingDeeper
- タグ:Golang,roadmap.sh,学習メモ
roadmap.sh > Go > Going Deeper > Marshalling & Unmarshalling JSON の学習を進めていきます。
※ 学習メモとしての記録ですが、後にこのセクションを学ぶ道しるべとなるよう、ですます調で記載しています。
contents
開発環境
- チップ: Apple M2 Pro
- OS: macOS Sonoma
- go version: go1.23.2 darwin/arm64
参考 URL
- [official] JSON
- [article] Guide to JSON in Golang
- [article] JSON to GO
- [article] Comprehensive Guide to using JSON in Go
Marshalling & Unmarshalling JSON
Marshalling(マーシャリング)と Unmarshalling(アンマーシャリング)は、Go のデータ構造と JSON 形式のデータを相互に変換する処理です。Marshalling は Go のデータ構造(構造体やマップなど)を JSON 形式に変換し、Unmarshalling はその逆で JSON から Go のデータ構造に変換します。これらの機能により、Web API でのデータのやり取りやファイルの入出力など、異なるシステム間でのデータ交換が可能になります。
Marshalling
Marshalling(マーシャリング)は、Go のデータ構造(構造体、マップ、スライスなど)を JSON 形式の文字列やバイト列に変換することです。つまり、プログラム内のデータを外部で使用可能な形式に変換する処理です。これは「エンコード」や「シリアライズ」とも呼ばれます。
基本的な使い方
Marshalling は encoding/json パッケージ の Marshal 関数を使用して、Go のデータ構造を JSON に変換します。
json.Marshal()
関数を使用して Go のデータ構造を JSON バイト列に変換する- Marshal 関数はエラーを返す可能性があるため、エラーハンドリングは必須
- 変換された JSON は
[]byte
型で返されるため、文字列として扱う場合は string 型に変換
package main
import (
"encoding/json"
"fmt"
)
// ユーザー情報を表す構造体
type User struct {
ID int `json:"id"` // JSON のキー名を指定
Name string `json:"name"`
IsActive bool `json:"is_active"`
}
func main() {
// 構造体にデータを格納
user := User{
ID: 1,
Name: "Alice",
IsActive: true,
}
// JSON に変換
jsonData, err := json.Marshal(user)
if err != nil {
// エラー処理
fmt.Println("Error marshalling JSON:", err)
return
}
// JSON を文字列として表示
fmt.Println(string(jsonData))
}
- 構造体定義
- User 構造体を定義し、各フィールドに JSON のキー名を指定するためのタグ(例:
json:"id"
) を追加しています。json:"フィールド名"
で JSON 出力時のフィールド名を指定できます。
- タグを使わない場合、フィールド名がそのままキー名として使用されます。
- User 構造体を定義し、各フィールドに JSON のキー名を指定するためのタグ(例:
- データの JSON 化
json.Marshal(user)
を使うと、user のデータが JSON 形式のバイト列([]byte
型)に変換されます。- 戻り値は
[]byte
型なので、string(jsonData)
で文字列に変換しています。
- エラー処理
json.Marshal
はエラーを返す可能性があるため、エラーチェックを必ず行います。
実行結果:
{"id":1,"name":"Alice","is_active":true}
インデント付き JSON
json.MarshalIndent()
を使用すると、整形された(インデントされた)JSONを生成できます。
user := User{
ID: 1,
Name: "John Doe",
IsActive: true,
}
jsonData, err := json.MarshalIndent(user, "", " ")
if err != nil {
fmt.Println("Error marshalling JSON:", err)
}
fmt.Println(string(jsonData))
第 2 引数はプレフィックス、第 3 引数はインデントの文字列を指定します。
{
"id": 1,
"name": "John Doe",
"is_active": true
}
以下はインデントにタブを使用した場合です。
jsonData, err := json.MarshalIndent(user, "", "\t") // タブを使用
{
"id": 1,
"name": "John Doe",
"is_active": true
}
func MarshalIndent | json package - encoding/json - Go Packages
ネストされた構造体
ネストされた構造体は、構造体内に別の構造体をフィールドとして含む形です。この基本形での JSON マーシャリングは、ネストされた構造体がそのまま JSON に変換されます。
package main
import (
"encoding/json"
"fmt"
)
type Address struct {
City string `json:"city"`
State string `json:"state"`
}
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Address Address `json:"address"` // ネストされた構造体
}
func main() {
// ネストされた構造体を含むデータを定義
user := User{
ID: 1,
Name: "Alice",
Address: Address{
City: "Osaka",
State: "Osaka Prefecture",
},
}
// JSON に変換
jsonData, err := json.MarshalIndent(user, "", " ")
if err != nil {
fmt.Println("Error marshalling JSON:", err)
return
}
// JSON を表示
fmt.Println(string(jsonData))
}
{
"id": 1,
"name": "Alice",
"address": {
"city": "Osaka",
"state": "Osaka Prefecture"
}
}
構造体タグのオプション
構造体のタグに様々なオプションを付与することができます。
omitempty
omitempty
をタグに指定すると、そのフィールドが ゼロ値(0、""、false、nil など)の場合に、JSON 出力からそのフィールドを省略します。デフォルト値を出力したくない場合に便利です。
package main
import (
"encoding/json"
"fmt"
)
type User struct {
ID int `json:"id"`
Name string `json:"name,omitempty"` // 空文字列なら省略
IsActive bool `json:"is_active,omitempty"` // false なら省略
Address string `json:"address,omitempty"` // 空文字列なら省略
}
func main() {
user := User{
ID: 1,
}
jsonData, _ := json.MarshalIndent(user, "", " ")
fmt.Println(string(jsonData))
}
omitempty
なしの場合:
{
"id": 1,
"name": "",
"is_active": false
}
omitempty
ありの場合:
{
"id": 1
}
ネストされた構造体と omitempty
ネストされた構造体に omitempty
を適用すると、ネストされたフィールドがゼロ値の場合、該当するフィールドを JSON 出力から省略できます。ただし、非ポインタ型の構造体ではゼロ値が空の構造体 {} として扱われるため、省略するにはポインタ型を使用する必要があります。
package main
import (
"encoding/json"
"fmt"
)
type Address struct {
City string `json:"city,omitempty"`
State string `json:"state,omitempty"`
}
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Address *Address `json:"address,omitempty"` // ポインタ型にする
}
func main() {
// Address フィールドを初期化しない(nil のまま)
user := User{
ID: 1,
Name: "Alice",
}
jsonData, err := json.Marshal(user)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println(string(jsonData))
}
{"id":1,"name":"Alice"}
非ポインタ型を利用した場合:
{"id": 1,"name": "Alice","address":{}}
-(フィールドを出力しない)
タグでキー名を -
に指定すると、そのフィールドは JSON 出力から常に除外されます。
type User struct {
ID int `json:"id"`
Password string `json:"-"` // パスワードを JSON に出力しない
}
{
"id": 1
}
カスタムエンコーディング
特定のフィールドをカスタム処理したい場合、json.Marshaler
インターフェースを実装することで独自の変換ロジックを定義できます。
package main
import (
"encoding/json"
"fmt"
"time"
)
type Event struct {
Name string `json:"name"`
Timestamp time.Time `json:"timestamp"`
}
func (e Event) MarshalJSON() ([]byte, error) {
type Alias Event // 再帰的に MarshalJSON を呼び出さないようにするための別名型
return json.Marshal(&struct {
Timestamp string `json:"timestamp"` // 独自フォーマット
*Alias
}{
Timestamp: e.Timestamp.Format("2006-01-02 15:04:05"),
Alias: (*Alias)(&e),
})
}
func main() {
event := Event{
Name: "Conference",
Timestamp: time.Now(),
}
jsonData, err := json.MarshalIndent(event, "", " ")
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println(string(jsonData))
}
{
"name": "Conference",
"timestamp": "2024-11-20 15:04:05"
}
html.EscapeString の抑制
デフォルトでは、Go の JSON 処理は安全性のために文字列内の <
や >
をエスケープします。これを抑制する場合は、json.Encoder
を使用し、SetEscapeHTML(false)
を設定します。
package main
import (
"encoding/json"
"fmt"
"os"
)
func main() {
data := map[string]string{
"html": "<div>Hello</div>",
}
encoder := json.NewEncoder(os.Stdout)
encoder.SetEscapeHTML(false) // HTML エスケープを無効化
encoder.Encode(data)
}
エスケープしている場合(通常の json.Marshal
):
{"html": "\u003cdiv\u003eHello\u003c/div\u003e"}
SetEscapeHTML(false)
を設定した場合:
{"html":"<div>Hello</div>"}
Map や Interface の利用
固定の構造体を使わず、動的にデータを扱う場合に便利です。
data := map[string]interface{}{
"id": 1,
"name": "Alice",
"tags": []string{"developer", "golang"},
}
jsonData, _ := json.MarshalIndent(data, "", " ")
fmt.Println(string(jsonData))
{
"id": 1,
"name": "Alice",
"tags": [
"developer",
"golang"
]
}
Unmarshalling
Unmarshalling(アンマーシャリング)は、JSON 形式の文字列やバイト列を Go のデータ構造に変換することです。つまり、外部から受け取った JSON データをプログラムで扱える形に変換する処理です。これは「デコード」や「デシリアライズ」とも呼ばれます。
基本的な使い方
Unmarshalling は encoding/json パッケージ の Unmarshal 関数を使用して、Go のデータ構造を JSON に変換します。
json.Unmarshal()
関数を使用して JSON を Go の構造体やマップに変換- JSON のデータ構造と Go のデータ構造のフィールド名を対応させる
- Unmarshal 関数もエラーを返す可能性があるため、エラーハンドリングが必要
package main
import (
"encoding/json"
"fmt"
)
// ユーザー情報を表す構造体
type User struct {
ID int `json:"id"` // JSON のキー名を指定
Name string `json:"name"`
IsActive bool `json:"is_active"`
}
func main() {
// JSON 形式のデータ
jsonStr := `{"id":1,"name":"Alice","is_active":true}`
// 構造体の変数を用意
var user User
// JSON を構造体に変換
err := json.Unmarshal([]byte(jsonStr), &user)
if err != nil {
// エラー処理
fmt.Println("Error unmarshalling JSON:", err)
return
}
// 変換されたデータを表示
fmt.Printf("%+v\n", user)
}
- JSON データ
- 文字列型の JSON データを用意します(例:
{"id":1,"name":"Alice","is_active":true}
)。 - JSON を Go のデータ構造に変換するには、
[]byte
型にする必要があるため、[]byte(jsonStr)
として渡します。
- 文字列型の JSON データを用意します(例:
- 構造体定義
- User 構造体を定義し、JSON のキー名に対応するタグ(例:
json:"id"
)を指定します。 - JSON のキー名と構造体フィールド名が一致していればタグは省略可能ですが、ケース(大文字/小文字)が異なる場合はタグが必要です。
- User 構造体を定義し、JSON のキー名に対応するタグ(例:
- Unmarshal 関数
json.Unmarshal
を使用して、JSON を Go の構造体に変換します。- 第 2 引数には、データを受け取る変数のポインタ(例:
&user
)を渡します。ポインタを渡さないと、関数内で値が変更されません。
- エラー処理
json.Unmarshal
はエラーを返す可能性があるため、エラーチェックを行います。
実行結果:
{ID:1 Name:Alice IsActive:true}
JSON のキーと構造体フィールド名の対応について
- JSON のキー名と構造体フィールド名が一致しない場合、タグ(例:
json:"key_name"
)を使って対応させる必要があります。 - JSON に存在しないフィールドは、構造体では初期値が設定されます(例: 整数型なら
0
、文字列型なら""
)。
構造体以外のデータ型も利用可能
Go の json.Unmarshal
は、構造体以外にもマップやスライスなどのデータ型に対応しています。これにより、動的な JSON データや配列形式の JSON を扱う場合に柔軟に対応できます。
マップへの変換
JSON のキーと値が動的で事前に構造体を定義できない場合、map[string]interface{}
を使って変換するのが便利です。
package main
import (
"encoding/json"
"fmt"
)
func main() {
// 動的な JSON データ
jsonStr := `{"id": 1, "name": "Alice", "is_active": true}`
// マップ型の変数を用意
var data map[string]interface{}
// JSON をマップに変換
err := json.Unmarshal([]byte(jsonStr), &data)
if err != nil {
fmt.Println("Error unmarshalling JSON:", err)
return
}
// 変換結果を表示
fmt.Printf("ID: %v\n", data["id"])
fmt.Printf("Name: %v\n", data["name"])
fmt.Printf("IsActive: %v\n", data["is_active"])
}
map[string]interface{}
は、JSON のキーを文字列、値を任意の型として扱う汎用的なデータ構造です。- 値の型が不明なため、型アサーション(例:
data["id"].(int)
)を使うことで型を明示的に変換する必要があります。
出力結果:
ID: 1
Name: Alice
IsActive: true
スライスへの変換
JSON 配列データを扱う場合、Go のスライス型に変換するのが便利です。
package main
import (
"encoding/json"
"fmt"
)
func main() {
// 配列形式の JSON データ
jsonArrayStr := `[{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]`
// スライス型の変数を用意
var users []map[string]interface{}
// JSON をスライスに変換
err := json.Unmarshal([]byte(jsonArrayStr), &users)
if err != nil {
fmt.Println("Error unmarshalling JSON:", err)
return
}
// 変換結果を表示
for _, user := range users {
fmt.Printf("ID: %v, Name: %v\n", user["id"], user["name"])
}
}
- JSON 配列は Go のスライス(
[]map[string]interface{}
)に対応します。 - 配列の各要素がマップで表現され、ループを使って要素ごとにアクセスできます。
出力結果:
ID: 1, Name: Alice
ID: 2, Name: Bob
インターフェース型への変換
JSON データが非常に動的で、トップレベルが配列かオブジェクトか分からない場合、interface{}
型に変換する方法があります。
package main
import (
"encoding/json"
"fmt"
)
func main() {
// 不明な形式の JSON データ
jsonStr := `{"id": 1, "name": "Alice", "tags": ["developer", "golang"]}`
// インターフェース型の変数を用意
var data interface{}
// JSON をインターフェース型に変換
err := json.Unmarshal([]byte(jsonStr), &data)
if err != nil {
fmt.Println("Error unmarshalling JSON:", err)
return
}
// 型アサーションで具体的な型にアクセス
if obj, ok := data.(map[string]interface{}); ok {
fmt.Printf("ID: %v\n", obj["id"])
fmt.Printf("Name: %v\n", obj["name"])
fmt.Printf("Tags: %v\n", obj["tags"])
}
}
interface{}
型を使うことで、JSON データが配列かオブジェクトかを実行時に判定できます。- 型アサーション(例:
data.(map[string]interface{})
)を使って適切な型にアクセスします。
出力結果:
ID: 1
Name: Alice
Tags: [developer golang]
まとめ
- Marshalling は、Go のデータ構造を JSON に変換する処理
- Unmarshalling は、JSON を Go のデータ構造に変換する処理
json.Marshal
は、Go のデータを JSON 形式にエンコードする関数json.Unmarshal
は、JSON を構造体やマップにデコードする関数- 構造体タグで JSON のキー名や出力形式をカスタマイズ可能
omitempty
を利用してゼロ値を出力から除外可能- ネストされた構造体や動的データに対応する柔軟な変換が可能
- カスタムエンコーディングで特定フィールドの変換処理を定義可能
SetEscapeHTML(false)
で HTML エスケープを抑制可能- マップやスライスを用いて動的な JSON データを扱うことが可能
[Next] Step 3-1: Urfave CLI
[Prev] Step 2-12: モジュール(Modules)