encoding/json/v2: new API for encoding/json
要約
概要
encoding/json/v2 および encoding/json/jsontext という2つの新しいパッケージをGo標準ライブラリに追加するproposalです。既存の encoding/json(v1)が抱える長年の設計上の問題を解消し、より正確・高速・柔軟なJSON処理を実現することを目的としています。Go標準ライブラリ史上最大規模のパッケージ改訂です。
ステータス変更
active → likely_accept
proposalレビューミーティングで提案者の@dsnetがAPI全体をウォークスルー形式で説明し、参加者全員から👍が得られたことにより、@aclementsがlikely acceptと判定しました。主な確認事項は、jsontextパッケージが安全性よりパフォーマンスを重視した低レベルAPIであること、Options型の設計がワーキンググループで徹底的に検討された末の結論であること、UnmarshalReadがEOFまで必ず読み進める点でv1の一般的なミスを防ぐ設計であることなどです。なお、本proposalはGo 1.27での安定版リリースを目標としており、Go 1.25でGOEXPERIMENT=jsonv2として先行公開済みです。
技術的背景
現状の問題点
v1 encoding/json には以下の設計上の問題が蓄積されていました。
- 無効なUTF-8を黙って置換文字に変換する(セキュリティリスク)
- JSONオブジェクト内の重複キーを許容する
nilのスライス/マップをJSONのnullとしてマーシャルする(空配列/空オブジェクトが自然)- フィールドマッチングが大文字小文字を区別しない(パフォーマンスと正確性の問題)
MarshalJSON/UnmarshalJSONがバイト列を返すためアロケーションが必須でストリーミング不可Marshal/Unmarshalにオプションを渡す手段がないio.Readerからデコード後にEOFチェックを忘れるバグが頻発
提案された解決策
JSON処理を「構文(syntactic)」と「意味(semantic)」の2層に明確に分離する2パッケージ構成を採用します。
encoding/json/jsontext — 構文層(Goのreflect非依存)
Encoder/Decoderによるトークン・値単位のストリーミング処理Token(個別のJSONトークン)とValue(完全なJSON値のバイト表現)の2つの基本型Pointer(RFC 6901 JSON Pointer)によるエラー位置の正確な報告
encoding/json/v2— 意味層(reflectを使用)Marshal/Unmarshalに加え、MarshalWrite/UnmarshalRead(io.Writer/io.Reader対応)、MarshalEncode/UnmarshalDecode(Encoder/Decoder対応)を追加- 新インターフェース
MarshalerTo/UnmarshalerFromによるアロケーションフリーのストリーミング実装 - ジェネリクスを使った呼び出し側カスタマイズ(
MarshalToFunc[T]/UnmarshalFromFunc[T]) Options型(可変長引数)で全関数に統一的なオプション注入
v1のv2実装による透過的な継続 — v1encoding/jsonはv2実装の上に構築され、DefaultOptionsV1()で従来の動作を再現します。v1のAPIと動作は変わりません。
これによって何ができるようになるか
1. アロケーションフリーのストリーミング処理で高パフォーマンスを実現
大量のJSONを扱うサービスで MarshalerTo / UnmarshalerFrom を実装することで、中間バイト列のアロケーションを排除できます。ベンチマークではUnmarshalがv1比で最大10倍の高速化が確認されています。
2. 呼び出し側から型ごとの変換ロジックを注入できる
これまで不可能だった「特定の型だけ独自のシリアライズ処理を適用する」ことが、コードを変更せずに実現できます。
3. オプションによる柔軟な動作制御
// Before: v1では動作を変更する手段がなかった
data, err := json.Marshal(v)
// After: v2ではオプションで動作を制御できる
data, err := jsonv2.Marshal(v,
jsonv2.Deterministic(true), // マップキーの順序を固定
jsontext.WithIndent(" "), // インデント出力
jsonv2.WithMarshalers(
jsonv2.MarshalToFunc(func(enc *jsontext.Encoder, t time.Time) error {
return enc.WriteToken(jsontext.String(t.Format(time.RFC3339)))
}),
),
)
4. 新しいstructタグによる表現力の向上
type User struct {
Name string `json:"name"`
CreatedAt time.Time `json:"created_at,omitzero,format:RFC3339"` // 新: omitzero, format
Internal string `json:"internal,inline"` // 新: inline
Config map[string]any `json:",inline"` // JSON objectのインライン展開
}
5. 正確なエラー情報
// v2ではエラー発生箇所をJSONポインタで特定できる
// &json.SemanticError{ByteOffset:42, JSONPointer:"/users/3/age", ...}
議論のハイライト
Options型の設計: 単一のOptionsインターフェース型を構文層・意味層・マーシャル・アンマーシャルで共用する設計は、ワーキンググループが多くの代替案(オプション構造体等)を徹底検討した末に採用されたものです。可変長引数での渡し方と後勝ちのマージ挙動が最も人間工学的と判断されました。jsontextは*Encoder/*Decoderの具体型を使用: インターフェースにすることで拡張性が増しますが、全トークン書き込みにバーチャルメソッドコールが発生しパフォーマンスが大幅に低下するため、具体型を採用しました。将来的にカスタム実装を登録できるAPIの追加は検討中です。- v1の動作互換性オプション群: v1の動作をv2上で再現するための
DefaultOptionsV1()には20個を超えるオプションが含まれています。これはHyrumの法則(意図せず安定した振る舞いが依存される現象)により多くのバグ的挙動がデファクトのAPIとなっていたためです。 omitemptyの再定義: v1ではGoの型システム(falsy値かどうか)で定義されていましたが、v2ではJSONの型システム(JSONとして空の値かどうか)で再定義しました。既存コードへの影響はboolや数値型のomitemptyに限られ、新設のomitzeroで同等の動作を実現できます。- スコープの絞り込み: ユーザー定義オプション、
[]byte/[N]byteのformat:stringサポートなど有益な機能はあえて初回リリースから除外し、安定したコアAPIの確定を優先しました。