1. Home
  2. Golang
  3. GuideToBecomingGoDeveloper
  4. GoingDeeper
  5. Marshalling & Unmarshalling JSON - Golang learning step 2-13

Marshalling & Unmarshalling JSON - Golang learning step 2-13

  • 公開日
  • カテゴリ:GoingDeeper
  • タグ:Golang,roadmap.sh,学習メモ
Marshalling & Unmarshalling JSON - Golang learning step 2-13

roadmap.sh > Go > Going Deeper > Marshalling & Unmarshalling JSON の学習を進めていきます。

※ 学習メモとしての記録ですが、後にこのセクションを学ぶ道しるべとなるよう、ですます調で記載しています。

contents

  1. 開発環境
  2. 参考 URL
  3. Marshalling & Unmarshalling JSON
  4. Marshalling
    1. 基本的な使い方
    2. インデント付き JSON
    3. ネストされた構造体
    4. 構造体タグのオプション
    5. カスタムエンコーディング
    6. html.EscapeString の抑制
    7. Map や Interface の利用
  5. Unmarshalling
    1. 基本的な使い方
    2. 構造体以外のデータ型も利用可能

開発環境

  • チップ: Apple M2 Pro
  • OS: macOS Sonoma
  • go version: go1.23.2 darwin/arm64

参考 URL

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))
}
  1. 構造体定義
    • User 構造体を定義し、各フィールドに JSON のキー名を指定するためのタグ(例: json:"id") を追加しています。
      • json:"フィールド名" で JSON 出力時のフィールド名を指定できます。
    • タグを使わない場合、フィールド名がそのままキー名として使用されます。
  2. データの JSON 化
    • json.Marshal(user) を使うと、user のデータが JSON 形式のバイト列([]byte 型)に変換されます。
    • 戻り値は []byte 型なので、string(jsonData) で文字列に変換しています。
  3. エラー処理
    • 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)
}
  1. JSON データ
    • 文字列型の JSON データを用意します(例: {"id":1,"name":"Alice","is_active":true})。
    • JSON を Go のデータ構造に変換するには、[]byte 型にする必要があるため、[]byte(jsonStr) として渡します。
  2. 構造体定義
    • User 構造体を定義し、JSON のキー名に対応するタグ(例: json:"id")を指定します。
    • JSON のキー名と構造体フィールド名が一致していればタグは省略可能ですが、ケース(大文字/小文字)が異なる場合はタグが必要です。
  3. Unmarshal 関数
    • json.Unmarshal を使用して、JSON を Go の構造体に変換します。
    • 第 2 引数には、データを受け取る変数のポインタ(例: &user)を渡します。ポインタを渡さないと、関数内で値が変更されません。
  4. エラー処理
    • 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)

Author

rito

  • Backend Engineer
  • Tokyo, Japan
  • PHP 5 技術者認定上級試験 認定者
  • 統計検定 3 級