メインコンテンツへスキップ

Go Proposal Weekly Digest

Go言語のproposal更新を毎週お届け

新機能挙動変更

encoding/json v1の設計上の問題を刷新する新パッケージ encoding/json/v2 と encoding/json/jsontext が追加され、既存の encoding/json もv2実装に置き換わった

New encoding/json/v2 and encoding/json/jsontext packages

この項目の注釈は AI により生成されており、誤りを含む場合があります。
使用例のコンパイル検証: 検証済み

概要

encoding/json/v2
encoding/json/jsontext という2つの新しい標準ライブラリパッケージが追加された。
json/v2 は既存の encoding/json(v1)の大改訂であり、MarshalMarshalWriteMarshalEncodeUnmarshalUnmarshalReadUnmarshalDecode を提供し、いずれも可変長の Options 引数でマーシャル/アンマーシャルの挙動を設定できる。
jsontext はJSONの構文処理だけを担う低レベルパッケージで、EncoderDecoderTokenValue 単位でJSONをストリーミング処理する。

v2はv1より厳格でRFC準拠寄りのデフォルト挙動を採る(不正なUTF-8を含む文字列を拒否する、JSONオブジェクト内の重複キーを拒否する、など)。差分の全体は encoding/json パッケージドキュメントの移行ガイドにまとまっている。

既存の encoding/json(v1)自体もv2実装の上に再実装された。マーシャル/アンマーシャルの挙動は保たれるが、エラーメッセージの文言が変わることがある。v1にもv2セマンティクスへ段階移行するための新しい Options が追加されており、v1 APIは今後も引き続きサポートされ、移行は必須ではない。Marshal性能は従来実装とほぼ同等、Unmarshal性能は大幅に高速化されている。

新実装との互換性問題に遭遇した場合は、ビルド時に GOEXPERIMENT=nojsonv2 を指定することで従来のv1実装に戻せる。このオプトアウトは将来のリリースで削除される予定。

GOEXPERIMENT期間中に、encoding/json/v2 のAPIにはいくつかの見直しが加えられた。
format タグオプション(golang/go#79071)、
unknown タグオプションと DiscardUnknownMembersgolang/go#77271)、
SkipFunc センチネルエラー(golang/go#74324)が削除された。
また inline タグオプションは embed に改名され(golang/go#79985)、string タグオプションの挙動(golang/go#79065)と
MatchCaseInsensitiveNames オプション(CL 792780)まわりの挙動も更新された。
encoding/json/jsontext では、数値系の Token アクセサがエラーも返すように変更された(golang/go#77666)。

導入経緯

本proposal(golang/go#71497)は、golang/go#63397 での長年の議論を経て正式なproposalとなったものである。

議論タイムライン

  • 2026年4月15日(2026年第16週の週次ダイジェスト): Proposal Review Meeting(@aclements、@adonovan、@cherrymui、@griesemer、@ianlancetaylor、@neild、@rolandshoemaker 出席)で active カラムに追加された。
  • 2026年第19週(週次ダイジェスト): 提案者の @dsnet がAPI全体をウォークスルーし、参加者全員の同意を得て @aclements が likely_accept と判定した。
  • 2026年第20週(週次ダイジェスト): 追加のコンセンサス変化がなかったため、正式に accepted となった。

議論のハイライト

  • Options 型の設計: 構文層(jsontext)・意味層(json/v2)・マーシャル・アンマーシャルで単一の Options インターフェース型を共用する設計は、working groupが多くの代替案(オプション構造体など)を検討した末に採用された。可変長引数として渡し、後から渡したオプションが優先されるマージ挙動が最も扱いやすいと判断された。
  • v1互換オプションの多さ: v1の挙動の多くが「実質的なバグ」でありながらHyrumの法則により事実上安定したAPIとして定着していたため、jsonv1.WithLegacySemantics 系のv1互換オプションが多数必要になった。
  • omitempty の再定義: v1ではGoの型システム(false0・nilポインタなど)で「空」を定義していたが、v2ではJSONの型システム(JSON null・空文字列・空オブジェクト・空配列)で再定義された。bool や数値型での従来の omitempty 相当の挙動は新設の omitzero への移行が推奨される。
  • format タグオプションの削除golang/go#79071): Go 1.28で型付き構造体タグが導入される見込みとなったため、より自然にそちらで表現できる format タグはworking groupの判断で削除された。実装自体は消えておらず、github.com/go-json-experiment/json 側の実験的なフックからは引き続き利用可能だが、標準の encoding/json/v2 パッケージとしては非サポートという位置付けになった。
  • unknown タグオプションと DiscardUnknownMembers の削除golang/go#77271): 類似機能を持つ inline タグとの違いが微妙すぎるとして、対抗提案(golang/go#76444)ではなくこちらの削除案がworking groupで採用された。アンマーシャル時に未知フィールドを拒否する RejectUnknownMembers は、v1の Decoder.DisallowUnknownFields 相当の互換性維持のため残された。
  • inline タグの embed への改名golang/go#79985): Kubernetesエコシステムを中心に、v1で意味を持たない ,inline タグが既に約65万件規模で使われていることが判明し、Go 1.27でv1にも inline タグの意味が付くと動作が変わってしまうリスクが指摘された。Goモジュールプロキシ全体の調査で embed という名前の既存使用は0件だったため、リスク回避のため embed に改名された。
  • string タグオプションの再帰動作廃止golang/go#79065): プロトタイプでは ,string タグがスライスなどの複合型に再帰的に適用されていたが、他のタグオプションはいずれも非再帰的であることや将来の型付き構造体タグとの整合性を理由に、複合型への ,string はエラーとする方針に変更された。
  • 数値系 Token アクセサへのエラー追加golang/go#77666): 従来案の Token.Float()1e500 のような範囲外の数値を黙って math.MaxFloat64 に丸めたり "Infinity"+Inf として受け入れたりしており、カスタムアンマーシャラーの実装者が気付かずに誤った値を使ってしまう懸念があった。最終的に strconv.ParseFloat と同様の意味論に合わせ、範囲外の値はエラーとして報告するよう変更された。

使用例

Before

package main

import (
	"encoding/json"
	"fmt"
)

type Response struct {
	Items []string `json:"items"`
}

func main() {
	r := Response{Items: nil}
	data, err := json.Marshal(r)
	if err != nil {
		panic(err)
	}
	fmt.Println(string(data)) // {"items":null}
}

After

package main

import (
	"fmt"

	jsonv2 "encoding/json/v2"
)

type Response struct {
	Items []string `json:"items"`
}

func main() {
	r := Response{Items: nil}
	data, err := jsonv2.Marshal(r)
	if err != nil {
		panic(err)
	}
	fmt.Println(string(data)) // {"items":[]}
}

移行時の注意

  • v1 encoding/json は引き続きサポートされ、既存コードの変更は不要。移行するかどうかは任意である。
  • v2のデフォルト挙動はv1と異なる点が複数ある(nilスライス/nilマップが null ではなく []/{} になる、不正なUTF-8やオブジェクトの重複キーがエラーになる、フィールド名マッチングが大文字小文字を区別するようになる、など)。詳細は encoding/json の移行ガイド を参照。
  • v1の挙動を維持したままv2のAPI(ストリーミングなど)だけを使いたい場合は、jsonv1.DefaultOptionsV1()jsonv2.Marshal/jsonv2.Unmarshal に渡すことで再現できる。
  • GOEXPERIMENT=jsonv2 を先行して試していた場合は、unknown タグオプション・DiscardUnknownMembersSkipFunc センチネルエラー・複合型への再帰的な ,string タグ・旧名の inline タグ(embed に改名)に依存したコードが正式版では動作しなくなる点に注意が必要。特に inline タグは黙って無視されるだけでエラーにはならないため、意図した通りに埋め込まれているか確認すること。
  • 新実装との互換性問題が出た場合は、ビルド時に GOEXPERIMENT=nojsonv2 を指定すると従来のv1実装に戻せる。ただしこのオプトアウトは将来のリリースで削除される予定であるため、恒久的な回避策にはできない。
  • 問題を発見した場合はIssueを立てることが推奨されている。

実装解説

encoding/json/jsontext の数値アクセサは、v1の慣習だった単一戻り値の float64/int64/uint64 から、エラーも返す (float64, error)/(int64, error)/(uint64, error) に変更されている(token.go;l=386Token.Floattoken.go;l=452Token.Inttoken.go;l=535Token.Uint)。

format タグオプションは encoding/json/v2 の構造体タグパーサ自体では引き続き構文解析されるが(fields.go;l=529)、time.Time など個別にフォーマット処理を持つ型以外でこのタグを指定すると、フィールド解析時点で Go struct field %s has unsupported \format` tag optionというSemanticError` が生成される(fields.go;l=258)。タグ自体をエラーとして拒否するのではなく、型ごとの対応状況に応じて後段でエラーにする設計になっている。

MatchCaseInsensitiveNames はグローバルなオプションだが、フィールドごとに case:strict/case:ignore タグを指定することでこのオプションの適用を上書きできる(options.go;l=204fields.go;l=370)。

関連リンク