Slide 1

Slide 1 text

encoding/json/v2のUnmarshalはこう変わった ~内部実装で見る設計改善 ~ Go Conference mini in Sendai 2026 @kurasawah 1

Slide 2

Slide 2 text

⾃⼰紹介 ● 倉澤 大樹(@kurasawah) ○ 株式会社ZOZOで検索機能を開発しています ○ GoとElasticsearchが好きです 2

Slide 3

Slide 3 text

このテーマを話そうと思ったきっかけ ● encoding/json/v2はGo1.25で実験的に追加された ● v2では「処理速度」「メモリ使用量」「アロケーション回数」 「エラー内容」が改善された ● 「なぜ改善されたのか?」を内部設計から理解したくなった ● 調べてみると、自分のコードにも活かせそう 3

Slide 4

Slide 4 text

今日話す4つの改善ポイント 1. パッケージ分離 2. エラーの原因特定 3. オブジェクトの再利用(sync.Pool) 4. 効率的なバッファ管理 4

Slide 5

Slide 5 text

v2のUnmarshal: 4つの改善ポイント 5 func Unmarshal(in []byte, out any, opts ...Options) (err error) { dec := export.GetBufferedDecoder(in, opts...) defer export.PutBufferedDecoder(dec) xd := export.Decoder(dec) err = unmarshalDecode(dec, out, &xd.Struct, true) return err } コード 改善ポイント export オブジェクト パッケージ分離 err エラーの原因特定 Get/PutBufferedDecoder オブジェクトの再利用(sync.Pool) Decoder 構造体 効率的なバッファ管理 src/encoding/json/v2/arshal.go

Slide 6

Slide 6 text

改善点1: パッケージ分離 v1: encoding/json 1パッケージに全機能が混在 v2: jsontext(構文層)+ json(意味層)に分離 パッケージ 層 例 jsontext 構文層 { , "name" , : , "Alice" ... json 意味層 "Alice" → User.Name なぜ良いか ● 責務が分かれ、変更の影響範囲が限定される ● 構文層(jsontext)を単独で利用できる 6

Slide 7

Slide 7 text

{"name":"Alice","age":"thirty"} → User.Ageはint型なのでエラー v1: cannot unmarshal string into Go struct field User.Age v2: cannot unmarshal JSON string into Go int within "/age" 改善点2: エラーの原因特定 なぜ原因特定が容易か ● JSONPointerで「どこで」、JSONKindで「何型が」失敗したか分かる &SemanticError{ … JSONPointer: "/age" , // どこで JSONKind: '"', // JSON型(string) GoType: reflect.TypeOf(int(0)), // 期待した型 ... } src/encoding/json/v2/errors.go

Slide 8

Slide 8 text

● v1: 毎回ゼロ値初期化 → 呼び出しごとにメモリ確保 ● v2: GetBufferedDecoder PutBufferedDecoder でプール管理 なぜ良いか : 高頻度呼び出し時のアロケーションとGCへの負担を削減 改善点3: オブジェクトの再利用 (sync.Pool) 1回目: Unmarshal({"name":"Alice",...}) ↓ Decoder取得 ↓ 処理 ↓ Poolへ戻す 2回目: Unmarshal({"name":"Bob",...}) ↓ 同じDecoder再利用 8

Slide 9

Slide 9 text

なぜ良いか : buf[prevStart:prevEnd] でコピーなしに前回値へアクセス可能 例: {"name":"Alice","age":30} を "Alice" まで読んだ時 → buf[prevStart:prevEnd] = "Alice" decodeBuffer — 1つの []byte を論理的に4分割 改善点4: 効率的なバッファ管理 9 type decodeBuffer struct { … buf []byte // 入力全体を保持するバッファ prevStart int // 前回読んだ値の開始位置 prevEnd int // 前回読んだ値の終了位置 rd io.Reader // 入力元(ストリーミング対応) } src/encoding/json/jsontext/decode.go

Slide 10

Slide 10 text

まとめ ● パッケージ分離 : jsontext + jsonによる責務の分離 ● エラーの原因特定 : SemanticErrorによる構造化エラー ● オブジェクトの再利用 : sync.Poolによるアロケーション削減 ● 効率的なバッファ管理 : decodeBufferによるコピーなしアクセス 10