Menu

Go言語のJSON:Marshal、Unmarshal、構造体タグ

encoding/jsonでGoのJSONをエンコード・デコードする方法を解説します。MarshalとUnmarshal、omitemptyやomitzeroなどの構造体タグ、整形出力、map[string]anyへのデコード、未知のフィールドの拒否、そしてDecoderによるストリーム処理まで。

このページのコードはエディタで実行できます - 編集してすぐに結果を確認できます。

MarshalとUnmarshal

json.Marshal はGoの値をJSONのバイト列に変えます。json.Unmarshal はJSONのバイト列からGoの値を埋めます。構造体タグでJSONのフィールド名を選びます。

最初の出力に Email がないのは、omitempty と空文字列のためです。Unmarshal にはポインタ(&back)が必要で、値そのものを渡すと InvalidUnmarshalError が返ります。

構造体タグ

タグの構文は json:"name,option,option" です。

タグ効果
json:"user_id"キーとして user_id を使う
json:"email,omitempty"false、0、""、nil、空のスライスやマップのときに外す
json:",omitzero"(Go 1.24)値がゼロ値のとき、または IsZero() がtrueを返すときに外す
json:"-"このフィールドを決してエンコードもデコードもしない
json:"-,"文字どおりの - をキーとして使う
json:"count,string"数値や真偽値をJSONの文字列("42")としてエンコードする
タグなしキーはGoのフィールド名 UserID になる

パスワードは構造体の外に出ず、残高はクォートされ、deleted_at は消えますが、created_at はゼロの時刻 0001-01-01T00:00:00Z を表示します。この違いが omitzero が追加された理由です。omitempty は構造体に対して機能したことがなく、time.Time では長年の驚きの種でした。Go 1.23以前では、*time.Timeomitempty を使えば同じ効果が得られます。

MarshalIndent(v, prefix, indent) は読みやすい出力を作ります。設定ファイルやデバッグに使い、APIではたいていコンパクトなJSONを送ります。

公開フィールドだけ

encoding/json はリフレクションを使い、公開されたフィールドしか見られません。これはGoで最もよくあるJSONのバグです。

type point struct {
	x, y int // lowercase: json.Marshal(point{1, 2}) gives {}
}

エラーも警告もなく、出力では {} になり、入力ではフィールドがゼロのまま残ります。フィールドを大文字にし、小文字のキーにはタグを付けます。

デコードでフィールドが対応づけられる仕組み

構造体にアンマーシャルするときは、次のようになります。

  • キーはタグの名前かフィールド名に、大文字小文字を区別せずに 対応づけられます。{"NAME": "x"}json:"name" とタグ付けされたフィールドを埋めます。
  • 対応するフィールドのないキーは、何も言わずに 無視されます
  • 対応するキーのないフィールドは 現在の値を保ちます。Unmarshalはリセットしないので、すでにデータを持つ構造体にデコードすると、そこに統合されます。
  • 型の不一致(構造体は int なのに文字列)は *json.UnmarshalTypeError を返しますが、他のフィールドは埋められます。

厳格なAPIや、打ち間違いのありうる設定ファイルなどで想定外のキーを拒否するには、DisallowUnknownFields を付けた Decoder を使います。

構造がわからない場合:map[string]any

形が事前にわからないときは、map[string]anyany にデコードします。対応は決まっています。

JSONGo
オブジェクトmap[string]any
配列[]any
文字列string
数値float64
true / falsebool
nullnil

出力には2つの罠が表れています。すべての数値は float64 なので、m["stock"].(int) は失敗します。そして2^53を超える整数は float64 では精度を失い、id 9007199254740993...992 として戻ってきます。UseNumber を使えば両方を避けられます。

構造の一部がわかっているなら、その部分を構造体にデコードし、残りには json.RawMessage を使います。これはフィールドの生のバイト列を保持するので、型がわかってから後でデコードできます。

スライス、マップ、ポインタ、nil

Goの値JSON
nil スライス(var s []intnull
空のスライス([]int{}[]
nil マップnull
nil ポインタnull
[]bytebase64の文字列
map[string]Tオブジェクト、キーはソートされる
map[int]T整数のキーを文字列にしたオブジェクト

nil と空のスライスの違いは、配列を期待するAPIクライアントにとって重要です。フィールドが [] でなければならないなら、[]T{}make([]T, 0) で初期化します。

入力で「ない」と「ゼロ」を区別する必要があるなら、ポインタのフィールド(*int*bool)を使います。アンマーシャルした後、nilのポインタはキーがなかったか null だったことを意味し、0 へのポインタはクライアントが 0 を送ったことを意味します。

ストリーム:EncoderとDecoder

json.MarshalUnmarshal はバイトスライス全体を扱います。io.Readerio.Writer(HTTPのボディ、ファイル、標準入力)には、json.NewDecoderjson.NewEncoder を使います。Decoderは、連続するJSONの値を1つずつ読むこともできます。

Encoder.Encode は各値の後に改行を書きます。デフォルトでは MarshalEncoder<>&\u003c\u003e\u0026 にエスケープするので、JSONをHTMLに埋め込んでも安全です。SetEscapeHTML(false) でこれを無効にできます。HTTPハンドラでは、json.NewDecoder(r.Body).Decode(&v)json.NewEncoder(w).Encode(v) がいつもの組み合わせです。

独自のエンコード

型は json.Marshalerjson.Unmarshaler を実装することで、自分のJSONを制御できます。よくあるのは、数値ではなく名前で表したいiotaの列挙型です。

MarshalJSON は値レシーバーなので、値とポインタの両方で動きます。UnmarshalJSON は値を変更するのでポインタレシーバーが必要です。文字列の形だけが必要な型は、代わりに encoding.TextMarshalerMarshalText)を実装でき、そうするとマップのキーとしても使えます。

よくある間違い

  • 小文字のフィールド名。 何も言わずに飛ばされます。
  • Unmarshal に値を渡す。 ポインタが必要です。
  • エラーを無視する。 不正なJSONや型の不一致は、エラーを通してしか報告されません。
  • map[string]any の数値が int だと思い込む。 float64 です。
  • omitempty が空の構造体やゼロの time.Time を落とすと期待する。 Go 1.24では omitzero を、それ以前ではポインタを使います。
  • マップのフィールドの順序に頼る。 マップのキーは出力時にソートされます。構造体のフィールドは宣言の順を保ちます。

よくある質問

Goで構造体をJSONに変換するには?

json.Marshal(v) を呼びます。[]byte とエラーを返します。含まれるのは公開されたフィールド(大文字で始まる名前)だけです。JSONのキーを制御するには、フィールドに json:"name" のような構造体タグを付けます。インデント付きの出力には json.MarshalIndent(v, "", " ") を使います。

なぜ構造体のフィールドがJSONの出力から消えるのですか?

encoding/json は公開されたフィールドしか見られないからです。name(小文字)という名前のフィールドは、マーシャルでもアンマーシャルでも見えません。フィールドを大文字にし、json:"name" のようなタグでJSONのキーを設定します。

GoのJSONのomitemptyは何をしますか?

omitempty は、フィールドが空の値(false0""、nilのポインタやインターフェース、空のスライスやマップ)を保持しているときに出力から外します。構造体や time.Time は空として扱いません。Go 1.24で追加された omitzero は、構造体や time.Time も含め、型のゼロ値である(または IsZero() メソッドがtrueを返す)値を外します。

Goで構造のわからないJSONをパースするには?

map[string]any(または any)にアンマーシャルします。オブジェクトは map[string]any、配列は []any、文字列は string、真偽値は bool、そしてすべての数値は float64 になります。値を読むには型アサーションを使い、大きな整数を正確に保つ必要があるなら Decoder.UseNumber を使います。

Coddy programming languages illustration

Coddyでコードを学ぼう

始める