Upgrade to Pro — share decks privately, control downloads, hide ads and more …

encoding/json/v2で何が変わるか

Avatar for glassmonenkey glassmonenkey
September 27, 2025
2.2k

 encoding/json/v2で何が変わるか

Go 1.25で導入されるjson/v2について、従来のencoding/jsonから「何が変わって、何が変わらないのか」を具体的なコード例で比較解説します。API、パフォーマンス、動作の違いを明確にし、開発者が実際に遭遇する変化点を整理してお伝えします。

Avatar for glassmonenkey

glassmonenkey

September 27, 2025
Tweet

Transcript

  1. 3 自己紹介 3 永野峻輔 (@glassmonekey) 所属 2024/02 ~ Ubie株式会社 Technology

    Platform パーソナライズ基盤 Tech Lead 趣味 個人開発, ゲーム, 読書 https://php-play.dev
  2. 日本の高い医療技術×テクノロジーから生まれた問診エンジン( AI)とプラットフォーム 6 創業者の思いから 生まれ独自のプラット フォームにより磨かれた 問診エンジン - 50名以上の医師監修のもと、 国内外5万本の医学論文を元に

    作られた問診エンジン - 1800以上の医療機関からの フィードバックにより 問診エンジンの精度を向上 問診エンジンをコア技術に据えたプラットフォーム 問診エンジン 月間1200万人以上の生活者が利用 メガファーマの約8割が活用 15,000件以上の医療機関と連携 生活者向け 医療機関向け 製薬企業向け
  3. 7 2020年のサービス提供開始以来、多くの方の適切な医療へのアクセスを支援しています 月間利用者数 1200万人 提携医療機関数 1万5000以上 累計利用回数 1億 8000 万回以上

    対応する症状 3500以上 ユビーを利用した後 実際に受診した人数(推計) 1838万人 対応する病名 1100以上 ユビーを利用したうち 「受診してよかった」 91.1% アカウント登録数 500万人
  4. 動作上の課題 標準仕様と安全性の問題 encoding/json v1には、JSON標準仕様への不完全な準拠と、潜在的なセキュリティリスクを引 き起こす複数の動作上の⽋陥が存在します。 • 重複キーの受理:JSONオブジェクト内の重複キーを受け⼊れてしまい、最後の値の みが残るため、セキュリティ上の脆弱性となる • 無効UTF-8の許容:RFC

    8259が有効なUTF-8を要求しているのに対し、現⾏の実装で はデータ破損リスクを招く • 大文字小文字無視: RFC 8259では区別するが現⾏の仕様では区別をしない • 一貫性の欠落: ポインタレシーバーのMarshalJSONで一貫性が欠落する • map/sliceのゼロ値: map/sliceのゼロ値はnullではなく{}/[]で扱いたい
  5. 動作上の課題例その1:重複キー問題の具体例 RFC 8259では、重複キー処理に関する具体的な規定はなく、各実装は「任意の値の選択」「値の結合」「値 の破棄」「エラー報告」などから選べます。この曖昧さは普遍的な意味付けを困難にします。 var m map[string]int err := json.Unmarshal([]byte(`{"x":1,"x":2}`),

    &m) fmt.Println(err) fmt.Println(m) // <nil> // map[x:2](最後の値が採用) この例では、同じキー "x" が2回出現していますが、エラーは発⽣せず、2回⽬の値が採⽤されます。v1 実装では⼊⼒の問題を黙って処理します。v2ではエラーになります。 セキュリティリスク: 採⽤値が⼊⼒順に依存して変動するため、署名検証‧監査‧差分適⽤のシナリオで問題が発⽣します。 CVE-2017-12635など、実際の脆弱性として悪⽤された事例もあります。
  6. 動作上の課題例その2:無効UTF-8の許容問題 RFC 8259では、全てのJSONテキストはUTF-8でエンコードされることを明確に規定しています。しかし、v1 のJSON処理は無効なUTF-8シーケンスを許容してしまいます。 // 無効なUTF-8シーケンスを含むJSON文字列 input := []byte(`{"key": "\uD800"}`)

    var result map[string]interface{} err := json.Unmarshal(input, &result) fmt.Println(err) fmt.Println(result["key"]) // <nil> // 不正なUTF-8文字が含まれた文字列 この例では、孤⽴したサロゲートペア(\uD800)を含む無効なUTF-8シーケンスがエラーなく処理されていま す。v1実装では、これらの問題を黙って受け⼊れてしまいます。v2ではこれもエラーになります
  7. 動作上の課題例その3:⼤⽂字⼩⽂字無視 RFC 8259では、キーは⼤⽂字⼩⽂字を区別すると定めていますが、Goのencoding/jsonでは構造体マッピン グ時に⼤⼩無視の⽐較を⾏うため、異なるキーが同⼀視されてしまい、解釈の不⼀致を⽣む要因となりま す。 type User struct { UserName

    string UserID int } data := []byte(`{"username":"admin", "UserID":345, "userid":12345}`) var user User err := json.Unmarshal(data, &user) fmt.Println(user, err) // {admin 12345} v1では「UserName」フィールドに「username」(⼩⽂字)のJSONキーがマッチします。前述の重複キーの問題と合わせ てuseridかUserIDのどちらを使うか不明瞭です。(前述の通り2回⽬の値が優先されます) v2では⼤⽂字⼩⽂字が⼀致するものが優先されます
  8. 動作上の課題例その4:map/sliceの零値 type User struct { Name string Hobbies []string Profile

    map[string]string } us := User{ Name: "John", } text, _ := json.Marshal(us) fmt.Println(string(text)) // {"Name":"John","Hobbies":null,"Profile":null} 外部システム連携を加味するとnullはエラーを誘発する要因になりえます。 加えて、空オブジェクト‧空配列になることが好ましいというアンケート結果が寄せられています。
  9. 動作上の課題例その4:⼀貫性の⽋落 type T bool func (v *T) MarshalJSON() ([]byte, error)

    { return []byte{'1'}, nil } type S struct { X T } func main() { v := S{true} e := json.NewEncoder(os.Stderr) e.Encode(v) // {"X":true} e.Encode(&v) // {"X":1} } https://github.com/golang/go/issues/22967 より ポインタレシーバー時に、vと&vで⼀貫性が失われる場合がある。 v1ではvが値のときはMarshalJSONが未実装扱いとなるので振る舞いに差がでる。
  10. APIの扱いにくさ • io.Readerからの正確なアンマーシャルが困難 - json.NewDecoder(r).Decode(v)では⼊⼒末尾のゴミデータ を拒否できない • オプション設定の柔軟性不⾜ - EncoderとDecoderのオプションがMarshalやUnmarshal関数で使⽤不可

    • ユーティリティ関数の柔軟性不⾜ - Compact、Indent、HTMLEscape関数は柔軟性に⽋ける出⼒設計 実務上の課題 安全なJSONパースには追加コードが必要 カスタム処理に必要な独⾃実装の増加 複雑なデータ構造処理のための冗⻑なボイラープレートコード
  11. APIの扱いにくさ:io.Readerアンマーシャル問題の具体例 r := strings.NewReader(`{"a":1} true`) dec := json.NewDecoder(r) var v

    map[string]int _ = dec.Decode(&v) if err := dec.Decode(&struct{}{}); err != io.EOF { return errors.New("extra data after top-level JSON value") } // 1値目のみ受理され、余剰データがあっても警告なし // 正しく実装するには追加のコードが必要 io.Readerから正確にアンマーシャルすることは難しく、標準的な⽅法では不⼗分です。 多くの開発者が json.NewDecoder(r).Decode(v) と書きますが、これは⼊⼒の末尾にある余分なデータを検出でき ません。 標準パッケージは余剰データを⾃動的に検出する機能を提供せず、追加コードが必要になります。
  12. APIの扱いにくさ:オプション設定の制限 // v1では各インスタンスに個別設定が必要 dec1 := json.NewDecoder(reader1) dec1.DisallowUnknownFields() dec2 := json.NewDecoder(reader2)

    dec2.DisallowUnknownFields() // Marshal/Unmarshalには同様のオプションがない data, err := json.Marshal(v) // カスタムUnmarshalJSONメソッドにはオプションが引き継がれない func (c *Custom) UnmarshalJSON(data []byte) error { } encoding/json v1では、伝搬できないケースや都度デコードの設定をする必要があるのでプロジェクト以下で ⼀貫した設定反映をやることに⽀障がある。 ある型が MarshalJSON / UnmarshalJSON を実装するとオプションが無効化され、同じデータ型でも結果が変 わる可能性がある。
  13. APIの扱いにくさ:ユーティリティ関数の柔軟性不⾜ src := []byte(`{"name":"John"}`) var buf bytes.Buffer _ = json.Compact(&buf,

    src) // json.Compact(os.Stdout, src) これはできない buf.WriteTo(os.Stdout) } ⼊⼒は []byte、出⼒は *bytes.Buffer 固定なので、⼊出⼒とも丸ごとメモリに載せる前提である。 そのため巨⼤データがそもそも扱いずらいし、ファイル書き込みなどの様々なユースケースへの対応する際に Io.Writerが直接使えないのでひと⼿間必要。
  14. パフォーマンス制約 encoding/json v1のAPI設計は、内部実装の詳細を別としても、パフォーマンス⾯での根本的な 制約を強いています。 • ストリーミング⾮対応: 実際には全JSONを内部バッファリング • 強制メモリアロケーション: MarshalJSON

    が[]byteを返すI/Fなので • ⼆度読み‧先読み強制: UnmarshalJSON の仕様で境界検知とパースで2回パース処理が⾛っている ⼆次関数的な性能劣化: 特にネストした構造体で各レベルがMarshalJSON/UnmarshalJSONを実装すると、再帰的な⼆重処理により計 算量が指数関数的に増加。OpenAPI仕様のパースなど複雑な構造では数秒→数分の遅延が発⽣。Kubernetesな ど実運⽤システムでも問題となっている(kubernetes/kube-openapi#315)。
  15. v2開発の歴史 2020年後半 同時期 2021年 2022年前半 Daniel Mart(encoding/jsonメンテナ)がv2の構想を最初にドラフト 2構想が⾮公式ながらGo開発者間で共有され始め、Google社外の有志も含めたプロジェクトとして動き出し 初期プロトタイプのデザインドキュメントが作成され、Go開発チーム内で議論開始 v2仕様の成熟と実運⽤での試⾏

    2023年 2024年 2025年 (現在) 公開発表とコミュニティでの議論。北⽶GopherConカンファレンスにて「The Future of JSON in Go」と題した講演 公式提案の準備と実験的実装の導⼊。Goプロジェクトの公式提案としてまとめ上げる 正式提案の承認とGo 1.25での実験的リリース
  16. v1との関係性 json/v2 新しい実装 encoding/json v2を基に再実装 統⼀実装: v1パッケージは内部的にv2の基に再実装されており、同じコードベースを共有 機能継承: v2に追加される後⽅互換機能はv1でも⾃動的に利⽤可能になる バグ修正共有:

    v2のバグ修正やパフォーマンス改善が両⽅のバージョンに適⽤される 重要:v1パッケージが廃⽌されることはありません。Go 1互換性の約束に基づき、v1の完全サポートは継続されます。
  17. 柔軟性をもたらす構造 package jsontext type Encoder struct { ... } func

    NewEncoder(io.Writer, ...Options) *Encoder func (*Encoder) WriteValue(Value) error func (*Encoder) WriteToken(Token) error type Decoder struct { ... } func NewDecoder(io.Reader, ...Options) *Decoder func (*Decoder) ReadValue() (Value, error) func (*Decoder) ReadToken() (Token, error) type Kind byte type Value []byte func (Value) Kind() Kind type Token struct { ... } func (Token) Kind() Kind • 拡張性:標準的なインターフェースでアプリケーション固有の要件に対応可能 package json func Marshal(in any, opts ...Options) (out []byte, err error) func MarshalWrite(out io.Writer, in any, opts ...Options) error func MarshalEncode(out *jsontext.Encoder, in any, opts ...Options) error func Unmarshal(in []byte, out any, opts ...Options) error func UnmarshalRead(in io.Reader, out any, opts ...Options) error func UnmarshalDecode(in *jsontext.Decoder, out any, opts ...Options) error jsontext(構⽂層) json/v2(意味層)
  18. 柔軟性がもたらす恩恵 ユースケースの拡⼤ 従来では[]byteしか対応してなかったところを、io.Reader/io.Writerに直接書き込めるようになった。 ストリーム処理といった凝ったことをしたいことを⾏いたい場合でも、jsontext.Encoder/jsontext.Decoderを使う ことでカバーできるようになった 26 package json func Marshal(in

    any, opts ...Options) (out []byte, err error) func MarshalWrite(out io.Writer, in any, opts ...Options) error func MarshalEncode(out *jsontext.Encoder, in any, opts ...Options) error func Unmarshal(in []byte, out any, opts ...Options) error func UnmarshalRead(in io.Reader, out any, opts ...Options) error func UnmarshalDecode(in *jsontext.Decoder, out any, opts ...Options) error json/v2(意味層)
  19. 豊富なオプション(⼀部抜粋) • jsontext.WithIndent … 出力のフォーマット内容にインデントを付与する ≒ json.MarshalIndent 26 dist, err

    := json.Marshal(src, jsontext.WithIndent("\t")) • json.OmitZeroStructFields … ゼロ値のフィールド (IsZero() boolメソッド = true)をエンコードするときに割愛させる dist, err := json.Marshal(src, json.OmitZeroStructFields(true))
  20. RFC 8259準拠の正確な検証 RFC 8259に完全準拠 JSONの仕様(RFC 8259)に完全準拠した⼊⼒検証が可能になりました。より厳格なバリデーションによ り、安全で正確なデータ処理を実現します。 • 不正なJSONフォーマット検出:形式的に不正なJSONを厳格に検出し処理を中⽌ •

    重複キー拒否:同⼀オブジェクト内の重複キーを検出して拒否、意図しないデータの上書きを防⽌ • 無効なUTF-8検出:RFC 8259が要求する有効なUTF-8のみを受け⼊れ RFC 8259 仕様準拠 :「JSONオブジェクト内のメンバーの名前は⼀意でなければならない」「JSONテキストはUTF-8でエン コードされなければならない」
  21. 厳密になった例 重複キーのエラー化 json/v2は最新のJSON標準仕様に厳格に準拠し、不正な⼊⼒に対する防御を強化しました。 // v1では重複キーを許容最後の値が採用される err := json.Unmarshal([]byte(`{ "name": "太郎",

    "name": "花子" }`), &data) fmt.Println(err) fmt.Println(data.Name) // v2では重複キーでエラー import "encoding/json/v2" err := json.Unmarshal([]byte(`{ "name": "太郎", "name": "花子" }`), &data) fmt.Println(err) // nil // "花子" // 重複キーエラー
  22. 厳密になった例 ⼤⽂字‧⼩⽂字のマッチの厳格化 json/v2では⼤⽂字⼩⽂字の区別をするようになりました。 // v1では大文字小文字の区別がない type User struct { Name

    string `json:"name"` } err := json.Unmarshal([]byte(`{"NAME": "太郎"}`), &user) fmt.Println(err) // nil fmt.Println(user.Name) // 太郎 // v2ではデフォルト大文字小文字をくべつする type User struct { Name string `json:"name"` } var user User err := json.Unmarshal([]byte(`{"NAME": "太郎"}`), &user) fmt.Println(err) // nil fmt.Println(user.Name) // “” // オプションにより互換性のある動作は可能 err = json.Unmarshal([]byte(`{"NAME": "次郎"}`), &user, json.MatchCaseInsensitiveNames(true) ) fmt.Println(user.Name) // 次郎
  23. 扱いが変わった例 map/sliceのnilの⾮null化 json/v2では直感的に期待されうる動作としてslice/mapのnull時の挙動が変わります // v1ではnilはnull type User struct { Name

    string Hobbies []string Profile map[string]string } payload, _ := json.Marshal(User{Name: "太郎"}) fmt.Println(string(payload)) // {"Name":"太郎","Hobbies":null,"Profile":null} // v2ではnilは空配列/空オブジェクト type User struct { Name string Hobbies []string Profile map[string]string } payload, _ := json.Marshal(User{Name: "太郎"}) fmt.Println(string(payload)) // {"Name":"太郎","Hobbies":[],"Profile":{}} // オプションにより互換性のある動作は可能 payload, _ = json.Marshal(User{Name: "次郎"}, json.FormatNilMapAsNull(true), json.FormatNilSliceAsNull(true), ) fmt.Println(string(payload)) // {"Name":"次郎","Hobbies":null,"Profile":null}
  24. コード例:基本APIとstructタグの互換性 v2は基本的なAPIの互換性を維持しつつ、structタグの使⽤⽅法も継承しています。v1からの移⾏時にコード の⼤幅な書き換えは必要ありません。 // v1 import "encoding/json" type User struct

    { Name string `json:"name"` Age int `json:"age,omitempty"` Email string `json:"-"` // 無視 Updated bool `json:",omitempty"` // フィールド名使用、空なら省略 } data, err := json.Marshal(user) // v2(互換性維持) import "encoding/json/v2" // 同じ構造体定義とタグ記法がそのまま使用可能 data, err := json.Marshal(user) v2では、v1と同じ基本APIやstructタグの記法をそのまま使⽤できます。これにより、既存コードから の段階的な移⾏が容易になります。
  25. ⼊⼒検証の厳密化 • v2の厳格なバリデーション 従来のv1では曖昧な⼊⼒が許容されていましたが、v2では既定で以下の厳密な検証を実施します: 重複キーを拒否:JSONオブジェクト内の重複名は既定でエラーに(CVE脆弱性対策) 無効UTF-8を拒否:RFC 8259準拠でUTF-8の厳密検証を実施 フィールド名は⼤⽂字⼩⽂字を区別:既定で厳密な名前⼀致を要求 • エラー種別の明確化

    検証エラーがより詳細になり、問題の切り分けが容易になりました。⼊⼒段階での早期検出により、下 流での予期せぬ不整合を防⽌します。 互換性が必要な場合は、明⽰的なオプションで検証を緩和できます: • jsontext.AllowDuplicateNames(true) • jsontext.AllowInvalidUTF8(true) • json.MatchCaseInsensitiveNames(true)
  26. 厳密になった例 (再掲) ⼤⽂字‧⼩⽂字のマッチの厳格化 json/v2では⼤⽂字⼩⽂字の区別をするようになりました。 // v1では大文字小文字の区別がない type User struct {

    Name string `json:"name"` } err := json.Unmarshal([]byte(`{"NAME": "太郎"}`), &user) fmt.Println(err) // nil fmt.Println(user.Name) // 太郎 // v2ではデフォルト大文字小文字をくべつする type User struct { Name string `json:"name"` } var user User err := json.Unmarshal([]byte(`{"NAME": "太郎"}`), &user) fmt.Println(err) // nil fmt.Println(user.Name) // “” // オプションにより互換性のある動作は可能 err = json.Unmarshal([]byte(`{"NAME": "次郎"}`), &user, json.MatchCaseInsensitiveNames(true) ) fmt.Println(user.Name) // 次郎
  27. ストリーミング対応を始めとした新I/F ストリーミング処理対応 従来のv1は「全体をバッファリングしてから処理」が基本でした。v2では真の逐次処理を実現: Reader/Writerに対する必要箇所のみの処理が可能 Token/Valueベースの低アロケAPI(GCプレッシャー軽減) Unmarshal性能は最⼤10倍⾼速化 package json func Marshal(in

    any, opts ...Options) (out []byte, err error) func MarshalWrite(out io.Writer, in any, opts ...Options) error func MarshalEncode(out *jsontext.Encoder, in any, opts ...Options) error func Unmarshal(in []byte, out any, opts ...Options) error func UnmarshalRead(in io.Reader, out any, opts ...Options) error func UnmarshalDecode(in *jsontext.Decoder, out any, opts ...Options) error
  28. io.Readerを使うことでメモリ効率の向上 // v1 func processJSON(r io.Reader) error { // 全データをメモリに読み込む必要がある

    data, err := io.ReadAll(r) if err != nil { return err } var value interface{} return json.Unmarshal(data, &value) } // v2 func processJSON(r io.Reader) error { // ストリーミング処理 dec := json.NewDecoder(r) var value interface{} return dec.Decode(&value) } v1では、ストリームからのデコードには全データを先 に読み込む必要があり、⼤きなJSONでメモリ消費が問 題になります。 v2では、ストリームから直接デコードでき、厳格な検証 も設定可能です。⼤規模JSONでもメモリ効率が向上し ます。
  29. トークン単位で扱い逐次的に処理をすることも可能 // v2での効率的なストリーム処理例 func streamProcess(r io.Reader) error { dec :=

    json.NewDecoder(r) // ストリームのトークンを順次処理 for { tok, err := dec.Token() if err == io.EOF { break } if err != nil { return err } // トークンごとの処理 fmt.Println(tok) } return nil } // 各要素を即時処理
  30. ⾮直感的な仕様の変更(ピックアップ) omitemptyの挙動変更 // v1 type Data struct { EmptySlice []int

    `json:"empty_slice,omitempty"` EmptyMap map[string]int `json:"empty_map,omitempty"` } // 空スライス・空mapは省略される // {"other_field": "value"} // v2 type Data struct { EmptySlice []int `json:"empty_slice,omitempty"` EmptyMap map[string]int `json:"empty_map,omitempty"` } // 空スライス・空mapは出力される // {"empty_slice":[],"empty_map":{}} v1では、omitemptyタグがあれば空スライスと空 マップはJSONから省略されます v2では、空スライスと空マップは初期化済みと⾒ なされ、JSONに出⼒されます
  31. ⾮直感的な仕様の変更(ピックアップ) map/sliceのnilの⾮null化 json/v2では直感的に期待されうる動作としてslice/mapのnull時の挙動が変わります // v1ではnilはnull type User struct { Name

    string Hobbies []string Profile map[string]string } payload, _ := json.Marshal(User{Name: "太郎"}) fmt.Println(string(payload)) // {"Name":"太郎","Hobbies":null,"Profile":null} // v2ではnilは空配列/空オブジェクト type User struct { Name string Hobbies []string Profile map[string]string } payload, _ := json.Marshal(User{Name: "太郎"}) fmt.Println(string(payload)) // {"Name":"太郎","Hobbies":[],"Profile":{}} // オプションにより互換性のある動作は可能 payload, _ = json.Marshal(User{Name: "次郎"}, json.FormatNilMapAsNull(true), json.FormatNilSliceAsNull(true), ) fmt.Println(string(payload)) // {"Name":"次郎","Hobbies":null,"Profile":null}
  32. json/v2の新規追加機能:統合オプションシステム 全APIで⼀貫して使えるオプション機能 新機能 すべてのAPIで opts ...json.Options パラメータを使⽤できるようになりました。これに より関数API、io.Reader/Writer、Encoder/Decoderのどの形式でも同じ⽅法でJSONの処理⽅法 をカスタマイズできます。 b,

    _ := json.Marshal(v, json.Deterministic(true), // 出力順序を一定にする json.RejectUnknownMembers(true) // 未知のフィールドを拒否 ) // Readerにも同じオプションを適用可能 err := json.UnmarshalRead(r, &out, json.RejectUnknownMembers(true)) )
  33. タグ機能の拡張 新しいタグ機能 v2では、従来より豊富なタグオプションが提供されます。複雑な表現や特殊ケースも公式なタグ記述 のみで対応可能になり、カスタム実装の必要性を⼤幅に減らします。 標準化された主要タグ 従来は⾃前処理で対応していた要件も、標準タグで直交的に表現できるようになりました。 これにより 保守性が向上し、コードの⼀貫性が⾼まります。 omitzero omitempty

    format inline unknown Goのゼロ値(=IsZero()がtrue)の場合に省略( omitemptyとは異なる判断基準) JSONの「空」値("", null, [], {} )の場合に省 略 time.Timeなどの日時フォーマット指定 (例: format:'2006-01-02') 構造体フィールドをインライン展開(ネストを平坦 化) 未知のフィールドの扱いを制御
  34. タグ機能の拡張:コード例 type User struct { ID int64 `json:"id,string"` // 数値を"123"で入出力

    Name string `json:"name"` UserID int `json:"userID,case:ignore"` // user_id なども受け入れ Count int `json:"count,omitzero"` // ゼロ値なら省略 Tags []string `json:"tags,omitempty"` // JSONで空なら省略 CreatedAt time.Time `json:"createdAt,format:RFC3339"` Extra map[string]any `json:",inline"` // 追加キーを親直下へ展開 } // Unmarshal: case:ignore で "user_id" → UserID に入る var u User _ = json.Unmarshal([]byte(`{"id":"123","user_id":7,"name":"A","x":1}`), &u) // Marshal: omitzero/omitempty/format/inline の挙動を確認 out, _ := json.Marshal(User{ ID: 123, Name: "A", CreatedAt: time.Date(2025, 9, 27, 15, 4, 5, 0, time.UTC), Extra: map[string]any{"x": 1}, }) fmt.Println(u.UserID) // => 7 fmt.Println(string(out)) // => {"id":"123","name":"A","createdAt":"2025-09-27T15:04:05Z","x":1}
  35. ステップ2&3:encoding/jsonからencoding/json/v2への置換&修正 インポートパスの置換⽅法 encoding/json"を"encoding/json/v2"に置き換えちゃいます。 GOEXPERIMENT=jsonv2 go test ./... でテストを実行して破壊的変更を探しました。 置換前(v1) 置換後(v2)

    import ( "encoding/json" "fmt" ) func process(data []byte) { var obj map[string]interface{} json.Unmarshal(data, &obj) } import ( "fmt" "encoding/json/v2" ) func process(data []byte) { var obj map[string]interface{} json.Unmarshal(data, &obj) }
  36. 実際の変更点と留意点:零値の扱いとomitemptyの挙動変更 零値の扱いの変化 移⾏時の注意点 実装例と⽐較 v2では空スライス‧空mapの処理結果と、omitemptyタグの挙 動が変更されています。 値の型 v1での結果 v2での結果 空スライス

    []string{} omitemptyで出⼒なし 空map map[string]string{} omitemptyで出⼒なし [] として出⼒ {} として出⼒ テストケースの期待値の⾒直しが必要です クライアントアプリケーションが特定の形式を期待している場合 は注意が必要です 既存のJSONパース処理が空配列や空マップの存在に依存してい る場合、ロジックの修正が必要です。 オプションを使って、後⽅互換を維持するか検討しましょう。 // 変更前(v1): type Data struct { EmptySlice []string `json:"empty_slice,omitempty"` EmptyMap map[string]string `json:"empty_map,omitempty"` } d := Data{ EmptySlice: []string{}, EmptyMap: map[string]string{}, } encoded, _ := json.Marshal(d) // 結果: {} // 空オブジェクト // 変更後(v2): // 同じコードで結果が変わる // 結果: {"empty_slice":[],"empty_map":{}}
  37. 実際の変更点と留意点:io.Reader/io.Writerを使う場合の変化 エンコード/デコード処理の変化 v2では、JSON出⼒時のインターフェースが簡略化されているので、追従する必要があります。 v1: NewEncoderを使う⽅法 v2: MarshalWriteを使う⽅法 v1: NewDecoderを使う⽅法 v2:

    UnmarshalReadを使う⽅法 func writeJSON(w io.Writer, data MyStruct) error { return json.NewEncoder(w).Encode(data) } import "encoding/json/v2" func writeJSON(w io.Writer, data MyStruct) error { return json.MarshalWrite(w, data) } func readJSON(r io.Reader) (MyStruct, error) { var result MyStruct err := json.NewDecoder(r).Decode(&result) return result, err } import "encoding/json/v2" func readJSON(r io.Reader) (MyStruct, error) { var result MyStruct err := json.UnmarshalRead(r, &result) return result, err }
  38. 公式JSONベンチマークの紹介 公式実験リポジトリの公開ベンチマーク結果の紹介です。Goエコシステム(CPU‧バージョン‧パラメータ等)の違いによる性能 差があることにご留意ください。実験はGo1.23.5で⾏われています。 参考: https://github.com/go-json-experiment/jsonbench • JSONv1: encoding/json v1.23.5 •

    JSONv1in2: github.com/go-json-experiment/json/v1 v0.0.0-20250127181117-bbe7ee0d7d2c • JSONv2: github.com/go-json-experiment/json v0.0.0-20250127181117-bbe7ee0d7d2c • JSONIterator: github.com/json-iterator/go v1.1.12 • SegmentJSON: github.com/segmentio/encoding/json v0.4.1 • GoJSON: github.com/goccy/go-json v0.10.4 • SonicJSON: github.com/bytedance/sonic v1.12.7 • SonnetJSON: github.com/sugawarayuuta/sonnet v0.0.0-20231004000330-239c7b6e4ce8
  39. パフォーマンス⽐較(参考) ⽐較値は全てJSONv1を基準(値=1)として正規化されています。低い値ほど⾼速です。 marshal(具体型): 1.4倍速い〜1.2倍遅い marshal(interface型): 1.6〜3.6倍⾼速。マップのキーソートを⾏わないことによる恩恵あり。 2.3〜5.7倍⾼速 JSONv2はJSONv1と⽐較して ケースまで様々。 marshal(RawValue型):

    unmarshal(具体型): 2.7〜10.2倍⾼速 JSONv2は他の主要実装と⽐較して 5.6〜12.0倍⾼速 JSONv2はJSONv1と⽐較して 。他実装と⽐べても最速クラス。 JSONv2はJSONv1と⽐較して だが、unsafe最適化を使う他実装より遅いケースも。 unmarshal(interface型): JSONv2はJSONv1と⽐較して で、ほとんどの他実装より⾼速。 詳細なグラフやデータ⽐較は公式リポジトリをご覧ください: https://github.com/go-json-experiment/jsonbench 倍10.2 倍から 21.1 倍⾼速 unmarshal(RawValue型):JSONv2はJSONv1と⽐較して で、ほとんどの他実装より⾼速か同等
  40. 正確性‧セキュリティの⽐較 各実装の安全性⽐較(参考情報) その他報告事項 実装 unsafe使⽤ UTF-8検証 重複キー検出 JSONv1(標準) なし 置換

    許容 JSONv2(新標準) なし エラー エラー JSONv1in2 なし 置換 許容 GoJSON あり 無視 許容 SonicJSON あり 無視 許容 安全性重視:JSONv2は厳密なUTF-8バリデーションと重複キーの拒否で、最も安全な実装 unsafe利⽤の代償:GoJSONやSonicJSONは⾼速だが、メモリ破損やパニックの報告あり 仕様準拠:JSONv2のみがRFC 8259に完全準拠(JWTなど認証関連での利点) テスト結果:GoJSONではランダムなpanicや失敗が報告されており、プロダクション環境では注意が必要