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.Time と omitempty を使えば同じ効果が得られます。
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]any か any にデコードします。対応は決まっています。
| JSON | Go |
|---|---|
| オブジェクト | map[string]any |
| 配列 | []any |
| 文字列 | string |
| 数値 | float64 |
| true / false | bool |
| null | nil |
出力には2つの罠が表れています。すべての数値は float64 なので、m["stock"].(int) は失敗します。そして2^53を超える整数は float64 では精度を失い、id 9007199254740993 は ...992 として戻ってきます。UseNumber を使えば両方を避けられます。
構造の一部がわかっているなら、その部分を構造体にデコードし、残りには json.RawMessage を使います。これはフィールドの生のバイト列を保持するので、型がわかってから後でデコードできます。
スライス、マップ、ポインタ、nil
| Goの値 | JSON |
|---|---|
nil スライス(var s []int) | null |
空のスライス([]int{}) | [] |
nil マップ | null |
nil ポインタ | null |
[]byte | base64の文字列 |
map[string]T | オブジェクト、キーはソートされる |
map[int]T | 整数のキーを文字列にしたオブジェクト |
nil と空のスライスの違いは、配列を期待するAPIクライアントにとって重要です。フィールドが [] でなければならないなら、[]T{} か make([]T, 0) で初期化します。
入力で「ない」と「ゼロ」を区別する必要があるなら、ポインタのフィールド(*int、*bool)を使います。アンマーシャルした後、nilのポインタはキーがなかったか null だったことを意味し、0 へのポインタはクライアントが 0 を送ったことを意味します。
ストリーム:EncoderとDecoder
json.Marshal と Unmarshal はバイトスライス全体を扱います。io.Reader や io.Writer(HTTPのボディ、ファイル、標準入力)には、json.NewDecoder と json.NewEncoder を使います。Decoderは、連続するJSONの値を1つずつ読むこともできます。
Encoder.Encode は各値の後に改行を書きます。デフォルトでは Marshal も Encoder も <、>、& を \u003c、\u003e、\u0026 にエスケープするので、JSONをHTMLに埋め込んでも安全です。SetEscapeHTML(false) でこれを無効にできます。HTTPハンドラでは、json.NewDecoder(r.Body).Decode(&v) と json.NewEncoder(w).Encode(v) がいつもの組み合わせです。
独自のエンコード
型は json.Marshaler と json.Unmarshaler を実装することで、自分のJSONを制御できます。よくあるのは、数値ではなく名前で表したいiotaの列挙型です。
MarshalJSON は値レシーバーなので、値とポインタの両方で動きます。UnmarshalJSON は値を変更するのでポインタレシーバーが必要です。文字列の形だけが必要な型は、代わりに encoding.TextMarshaler(MarshalText)を実装でき、そうするとマップのキーとしても使えます。
よくある間違い
- 小文字のフィールド名。 何も言わずに飛ばされます。
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 は、フィールドが空の値(false、0、""、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 を使います。