メインコンテンツへ移動

JSONをGoに変換

「JSONをGoに変換」は、サンプルのドキュメントから構造体をブラウザ内で生成します。すべてのフィールドはエクスポートされ、明示的なjsonタグを持ちます。encoding/jsonはエクスポートされていないフィールドを黙って無視します。生成した構造体が空のオブジェクトにマーシャルされる原因として最も多いのは、小文字始まりのフィールド名です。

端末内で処理
サンプルJSON
0 B1 行行 1、列 1
構文エラー · 1行1列でエラー

ドキュメントが途中で終わっています。切れている可能性があります。


^
生成されたコード読み取り専用
0 B1 行
複数のレコードがあれば貼り付けてください。一部の行にだけあるフィールドを検出し、正しい省略可能な型を生成できます。

JSONをGoに変換はどう動きますか?

生成されるGoの構造体を「正しい」にも「静かに壊れている」にも分ける決断が3つあります。3つともコードの見た目ではなく、encoding/jsonが実行時に何をするかに関わるものです。

すべてのフィールドはエクスポートされ、タグを持つ

Goがマーシャルするのはエクスポートされたフィールドだけです。idというフィールドを持つ構造体は、json.Marshalからは{}が出力され、json.Unmarshalの後もidはゼロのままです。どちらの場合もエラーは出ません。そこで、すべての名前を大文字始まりにし、元のキーを持つ明示的なjson:"…"タグを付けます。頭字語はGo自身のスタイルガイドに従います。user_idはUserIdではなくUserIDに、api_urlはAPIURLになります。

省略可能なフィールドはポインタにする

素のstringはJSONのnullを保持できず、存在しないキーと空のキーの区別もできません。どちらも""として届くからです。素のintでは、カウントの欠落とゼロのカウントを区別できません。そのため、省略可能なフィールドとnullになり得るフィールドはポインタにします。この区別を保てるGoの構文はポインタだけです。スライス、マップ、interface{}はもともとnilになり得るので、無意味な二重の間接参照を与えるのではなく、そのままにします。

intが保持できない整数はint64になる

intはプラットフォームによっては32ビットです。スノーフレークIDやミリ秒のタイムスタンプはオーバーフローし、そのバグはintが狭いプラットフォームでしか現れません。2,147,483,647を超える値はint64として出力します。

ユニオンはinterface{}になる。Goには直和型がないため

文字列と数値の両方を含む配列を記述できるGoの型はありません。無理に型をでっち上げるジェネレーターは、アンマーシャルに失敗するコードを出力します。interface{}が正直な答えで、直すべき場所は構造体ではなくAPIの側です。

出力はすでにgofmt整形済み

名前、型、タグ、末尾のコメントは、揃ったカラムになるようパディングされます。そうしないと、CIで最初にgofmtが走ったとき、誰も編集していないファイルにdiffが発生します。

サンプル

[
  { "user_id": 900719925474099, "api_url": "https://x.dev" },
  { "user_id": 12, "api_url": "https://y.dev", "note": "hi" }
]

生成結果

type User struct {
	UserID int64   `json:"user_id"`
	APIURL string  `json:"api_url"`
	Note   *string `json:"note,omitempty"` // optional
}

JSONをGoに変換が対応するオプションとエッジケースは?

技術リファレンスとオプション
パラメータ型デフォルト動作とエッジケース
パッケージ識別子mainファイルの先頭にあるパッケージ節です。出力先のファイルが存在するパッケージに設定してください——models, api, internal/store など。
ポインタ真偽値onオプションのフィールドと null 許容フィールドが *T になり、キーの不在とゼロ値を区別できるようになります。これをオフにすると構造体がシンプルになり、欠落したデータが見えなくなります。
omitempty真偽値onオプションのフィールドに ,omitempty を追加し、マーシャルし直すときに存在しないようにします。omitempty は、まさにその理由で、本物の空文字列やゼロ値もドロップする点に注意してください。
json.Number真偽値off非整数値の場合、float64 ではなく json.Number を出力し、リテラルの桁数を保持します。通貨や、float64 の正確な範囲を超える引用符なしの数値として到着する ID には有効です。
stringGo型string検出された形式(uuid, date-time, email, uri)は、カスタムアンマーシャラーなしでは encoding/json がタイムスタンプを time.Time にパースしないため、型にマッピングされるのではなくコメントとして記述されます。
integerGo型int / int64デフォルトは int; サンプルのいずれかが 2,147,483,647 を超える場合は int64。切り捨てられた ID は、本番環境で現れるバグです。
numberGo型float64小数部分を持つ値。整数と浮動小数点数が同じフィールドにレコード間で現れる場合、そのフィールドは float64 になります。
ネストしたオブジェクトGo型*Named独自の構造体に昇格され、ポインタで参照されるため、代入時のコピーを避け、フィールドを nil にできます。同一の形状の構造体は一つ共有されます。

よくある質問

JSONのキーが小文字なのに、すべてのフィールドが大文字始まりになるのはなぜですか?

encoding/jsonが見るのはエクスポートされたフィールドだけで、Goでエクスポートとは先頭が大文字であることだからです。小文字始まりのフィールドはMarshalでもUnmarshalでも、エラーなしでスキップされます。構造体はコンパイルも実行もされるのに、何もしません。jsonタグが元のキーを持つので、通信上の形式は変わりません。

ポインタは必要ですか?

「存在しない」と「ゼロ」が違う意味を持つ場所では、すべて必要です。nilの*intはキーが存在しなかったことを意味し、0のintはどちらとも取れます。retry_countやdiscountのようなフィールドでは、たいていこの区別が重要です。常に存在すると分かっているフィールドにはノイズなので、ポインタをオフにするのは合理的です。

タイムスタンプがtime.Timeではなくstringになるのはなぜですか?

encoding/jsonは、任意の形式のタイムスタンプを自力ではtime.Timeにパースしないからです。対応しているのはRFC 3339だけで、しかもフィールドがtime.Timeと宣言されている場合に限られ、別の書式が来ると固く失敗します。サンプルからtime.Timeを生成すると、日付の書式が違う最初のレコードでパニックを起こす構造体ができあがります。検出された書式はコメントに注記してあるので、意図してその選択ができます。

構造体の中のinterface{}は何をしているのですか?

そのフィールドは、サンプル間で本当に異なる型を持っていました。あるレコードではstring、別のレコードではオブジェクトです。Goにはこれを表現できる型がありません。選択肢は、カスタムのUnmarshalJSONを書く、後でデコードするjson.RawMessageにする、API側を直す、のいずれかです。生成したユニオン型では、単にアンマーシャルが失敗するだけです。

gofmtが出力を書き換えることはありますか?

ないはずです。フィールド名、型、タグ、末尾のコメントは、揃ったカラムになるようパディングされています。これはまさにgofmtが構造体に対して行うことです。それでもgofmtがdiffを出すケースを見つけたら、それはスタイルの好みではなく、報告する価値のあるバグです。

JSONはアップロードされますか?

いいえ。パースも型推論もコードの生成も、すべてブラウザ内で実行されます。どこにも何も送られません。これは重要です。構造体ジェネレーターに貼り付けられるサンプルは、たいてい統合相手のシステムからの実際のAPIレスポンスだからです。