
2026/11/05 2:52
JSON の新しい Go API
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
2026 年 11 月 4 日、Google は公式に Go 1.27 をリリースし、
encoding/json/v2 および encoding/json/jsontext の安定版を公開しました。Michael Pratt、Joe Tsai などによるこのアップデートは、15 年以上前に初期開発から存在した元の JSON ライブラリの長年残存していた問題を解決します。主な改善点には、無効な UTF-8 を受け付けるなどの動作上の欠陥の修正(重複するメンバー名 (CVE-2017-12635)、スライサーまたはマップの nil 性をリークする問題、大文字小文字区別を気にしないマーシャリング、一貫性のないメソッド呼び出し)が含まれます。新しい encoding/json/jsontext パッケージは反射を用いない純粋なストリーミング構文処理を可能にし、v1 のパフォーマンス制限(必須のアロケーション、再帰呼び出しによる二次的課題など)に対処します。更新された encoding/json/v2 は新しいインターフェース (MarshalerTo, UnmarshalerFrom) を導入し、これにより Encoder または Decoder を通じてストリーミング処理が可能になります。さらに、型定義されたメソッドよりも優先されるカスタム表現オプション (WithMarshalers, WithUnmarshalers) も提供します。v2 の主な動作変化には、無効な UTF-8 および重複する名前でエラーを報告し、nil スライサーまたはマップを空の配列/オブジェクトとしてマーシャリングし、大文字小文字区別マッチングを強制し、omitempty を再定義し、time.Duration のシリアライゼーションにオプションが必要とすることなどが含まれます。パフォーマンスの面で、マーシャリング速度は v1 と比較して最大 10 倍向上し、マーシャリング速度は同等です。移行は段階的に行うことを意図しており、v1 および v2 は互換性があり(v1 は v2 のオプションを通じて実装されているため)、既存のコードを破綻させずに大文字小文字区別パースなどの新機能を順次取り入れることができるように設計されています。これにより、データ処理パイプラインの効率的な近代化を最小限の中断で行うことが可能になります。本文
Go の JSON パッケージ再編:encoding/json/v2
と jsontext
の導入と解説
encoding/json/v2jsontextマイケル・プラット、ジョー・チャイ、ダニエル・マルティ、ヨハン・ブランドホスト=ザツコーン、ロジャー・ペッペ、クリス・ハインズ、そしてダミアン・ネイルによる投稿 日付:2026 年 11 月 4 日
序論
JavaScript Object Notation(JSON)はインターネット上で最も広く利用されているデータ形式の一つです。Go プログラムでも多数が読み書きしており、標準ライブラリには JSON サポートが組み込まれています。Go 1.0 よりも以前からこの対応が存在し、執筆から15 年以上前に JSON サポートに関する記事を掲載したこともあります!
パッケージは時間の経過とともにユーザーのニーズに合わせて進化していきます。
encoding/json パッケージも例外ではなく、昨年には待ちに待った改修と修正をもたらす新たな実験的 API の導入を発表しました。さらなるフィードバックと精査を経て、新しい encoding/json/v2 と encoding/json/jsontext パッケージは安定化し、Go 1.27において一般利用が可能となりました。
本稿では、実験の導入を説明し、
encoding/json の問題点を詳述し、新パッケージを紹介いたします。
encoding/json
の課題
encoding/json全体として、
encoding/json はよく機能してきましたが、多くのユーザーが欠陥や改善点を見出しました。
動作上の不具合
- JSON SYNTAX の不十分な扱い
- 最新インターネット標準(RFC 8259)では、有効な UTF-8 を要求し、重複名は未定義とされます。
は現在無効な UTF-8 や重複したメンバー名を受け付けており、データ破損や**セキュリティ脆弱性(CVE-2017-12635)**を引き起こす可能性があります。encoding/json- これらの行為はデフォルトでエラーを報告するべきです。
- スライスとマップの
性のリークnil
はencoding/json
スライスまたはマップを JSON のnil
として marshal しますが、他方の実装との通信で unmarshal に際してエラーを引き起こします。null- 多くのユーザーがデフォルトで
スライスとマップを空の JSON アレイやオブジェクトとして marshal することを好みます。nil
- 大文字小文字不感応な unmarshal
- メンバー名の照合がケース不感応であり、予期せぬデフォルト挙動および潜在的なセキュリティ脆弱性となります。また、パフォーマンス上の制約にもなります。
- メソッド呼び出しの一貫性の欠如
- ポインタ受容器に宣言された
メソッドが一貫性なく呼び出されることがあります。これはバグですが、既存の依存関係のため修正は困難です。MarshalJSON
- ポインタ受容器に宣言された
API の欠陥
から正しく unmarshal することが困難です(入力末尾の不要なデータを拒否できません)。io.Reader- オプションは
およびEncoder
タイプには設定可能ですが、Decoder
とMarshal
関数やインターフェースでは利用できません。Unmarshal- 例えば、
メソッドを呼び出す際に、Custom UnmarshalJSON
の効果が失われます。Decoder.DisallowUnknownFields
- 例えば、
、Compact
、およびIndent
関数はHTMLEscape
に書き出されるため、柔軟性に欠けます。bytes.Buffer
パフォーマンスの制限
- MarshalJSON: 戻り値の
を割り当てることが強制され、有効性検証やフォーマット化も内部で行う必要があります。[]byte - UnmarshalJSON: 完全な JSON 値全体をパースして終了位置を特定してから呼び出す必要があり、二度のパースが発生します。
- ストリーミング機能の欠如:
およびEncoder
は完全に JSON 値をメモリにバッファリングします。個別トークン処理用の API も不足しており、再帰的に呼び出された場合パフォーマンスが劣化します。Decoder
encoding/json/v2
の導入方針
encoding/json/v2パッケージの新しい互換性のないメジャーバージョンを導入することは大きな決断です。原則として既存の API を修正すべきですが、这些问题は既存の API に本質的に起因するため、Go 1 の互換性を保証する枠組み内で実質的に解決不可能でした。
そこで、
encoding/json/v2(以下「v2」)へと至りました。これにより、encoding/json(以下「v1」)とは独立した v2 の名前空間内で変更を行うことができました。
encoding/json/jsontext
を基盤として
encoding/json/jsontextGo における JSON シリアライゼーションは以下の 2 つの要素に分解できます:
- 文法的機能: JSON の文法に基づいて処理を行う機能(「encode」「decode」)。
- セマンティックス機能: JSON 値と Go 値との関係を定義する機能(「marshal」「unmarshal」)。
この分離により、
encoding/json/jsontext パッケージを新設します。これは純粋にエンコードに関する機能を扱い、Go の反射には依存せず、真にストリーミング方式で動作します。
jsontext
の基本 API
jsontextpackage 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 // jsontext.Value への型エイリアス func (Value) Kind() Kind type Token struct { ... } func (Token) Kind() Kind
- JSON 値: 名前付き
で表され、文法的に一つ以上の JSON トークンから構成されます。[]byte - JSON トークン: 不透明な
タイプで表され、割り当てなしで任意のトークンを表現できます。Token
これにより、
MarshalJSON および UnmarshalJSON のパフォーマンス問題を解決し、検証や境界決定の責任をライブラリ側に移すことができました。
encoding/json/v2
の導入
encoding/json/v2v1 の問題点を修正しつつ、v1 パッケージの利用者に馴染みを持たせるように設計されています。移行時の利用方法はほとんど変わりません。
v2 の基本 API
package json // Marshal / Unmarshal 関数に Options を受け取ります func Marshal(in any, opts ...Options) (out []byte, err error) func Unmarshal(in []byte, out any, opts ...Options) error // io.Writer / io.Reader を直接操作 func MarshalWrite(out io.Writer, in any, opts ...Options) error func UnmarshalRead(in io.Reader, out any, opts ...Options) error // Encoder / Decoder を直接操作(内部実装) func MarshalEncode(out *jsontext.Encoder, in any, opts ...Options) error func UnmarshalDecode(in *jsontext.Decoder, out any, opts ...Options) error
v1 と異なり、オプションは marshal/unmarshal 関数の一次引数として扱われ、柔軟性と設定可能性が大幅に向上しました。
タイプ指定のカスタマイズ
v2 でも特定のインターフェースを満たすことでタイプが独自の JSON 表現を定義できます。
type Marshaler interface { MarshalJSON() ([]byte, error) // v1 と同一 } type MarshalerTo interface { MarshalJSONTo(*jsontext.Encoder) error // 新規:オプションチェーンとの連携が可能に } type Unmarshaler interface { UnmarshalJSON([]byte) error // v1 と同一 } type UnmarshalerFrom interface { UnmarshalJSONFrom(*jsontext.Decoder) error // 新規:Decoder オプションの利用可能に }
呼出者指定のカスタマイズ
v2 では、
Marshal および Unmarshal の呼出者が任意のタイプのためのカスタム JSON 表現も指定できます。呼出者指定関数は優先されます。
func WithMarshalers(*Marshalers) Options type MarshalFunc[T any](fn func(T) ([]byte, error)) *Marshalers type MarshalToFunc[T any](fn func(*jsontext.Encoder, T) error) *Marshalers func WithUnmarshalers(*Unmarshalers) Options type UnmarshalFunc[T any](fn func([]byte, T) error) *Unmarshalers type UnmarshalFromFunc[T any](fn func(*jsontext.Decoder, T) error) *Unmarshalers
動作の違い
v1 の問題を解決するために一部の動作が変更されています:
- 無効な UTF-8: エラーを報告します。
- 重複名: JSON オブジェクトに重複名が含まれている場合にエラーを報告します。
スライス/マップ: 空の JSON アレイまたはオブジェクトとして marshal します。nil- ケース感応な照合: JSON メンバー名から Go フィールド名への照合をケース感応に行います。
: 再定義され、omitempty
、null
、""
、[]
としてエンコードされる場合フィールドを省略するように機能します。{}
: シリアライゼーションを試みたらエラー(デフォルト表現未実装)。time.Duration
パフォーマンス最適化
: v1 と同等です(場合によりわずかに高速・遅いこともあります)。Marshal
: v1 よりも著しく高速で、ベンチマークでは最大10 倍の向上が示されています。Unmarshal
より大きなパフォーマンス向上には、既存の実装を
MarshalerTo および UnmarshalerFrom も実装する方向に移行する必要があります。これにより純粋にストリーミング方式での処理が可能になります(例:Kubernetes の OpenAPI パースで桁違いの改善)。
詳細は
リポジトリをご覧ください。go-json-experiment/jsonbench
互換性
Go 1 の互換性は核心的な保証です。v1 と v2 はともに Go 1 の互換性を保証の対象としています。
- 移行の是非: v1 を使用しているアプリケーションは長期的にサポートされ、必ずしも v2 への移行を行う必要はありません。
- エコシステムのスムーズな移行: v1 と v2 が互換性を持ち、異なるバージョンを使用するパッケージが共同で動作します。
- v2 パッケージが v1 の
を使用する場合でも機能します。MarshalJSON - v1 パッケージが v2 の
をサポートする場合でも機能します。MarshalJSONTo
- v2 パッケージが v1 の
この互換性は、v2 を基盤として実装する新しいパッケージを設計することで達成されました。v1 の互換性オプションをすべて渡すことで、v2 を用いて v1 を実装可能です。
移行
段階的な移行が可能なため、呼び出し場所をゆっくりと移行できます。
- 可視的な動作変更なし: 多くのアプリケーションは直接 v2 に移行できます。
- 必要な挙動のみ変更: v1 挙動に依存する場合は、オプションで制御可能です。
例:重複名を受け付ける v2 の挙動を除くすべての v2 挙動を選択する場合:
json.Marshal(..., json.DefaultOptionsV2(), jsontext.AllowDuplicateName(true))
詳細については**「v2 への移行」ガイド**を参照してください。
さらに情報
- 「Working with JSON」チュートリアル
およびencoding/json/v2
パッケージのドキュメントencoding/json/jsontext
パッケージの v2 比較ドキュメントencoding/json- 「v2 への移行」ガイド
長年の課題
この計画は約 6 年かかりました。Go プロジェクトは、Google に雇用されていない人々によって行われた協力努力であり、活発なグローバルコミュニティによるものです。
- 起源: 2020 年末、既存パッケージの問題を修正できない事情からダニエル・マルティ氏が仮想的な v2 の草案を開始しました。
- 動機: ジョー・チャイ氏は
パッケージのカスタム実装への失望から、厳格な JSON 標準とストリーミング処理の必要性を痛感し、v2 との融合を推進しました。protojson - 貢献者: マイケル・プラット、ロジャー・ペッペ、クリス・ハインズ、ヨハン・ブランドホスト=ザツコーン、ダミアン・ネイルら多くの開発者が設計レビュー、コードレビュー、回帰テストを提供しました。
2023 年末の GopherCon で発表され、2025 年初めの正式な提案を経て、Go 1.25 で
encoding/json/v2 を Go 実験として採用し、現在では一般利用(Go 1.27)に至りました。