Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 15009] ファイルの概要

このコミットは、Go言語の標準ライブラリ encoding/json パッケージに Decoder.Buffered アクセサを追加するものです。これにより、json.Decoder が内部的に読み込んだがまだ処理していない、基となる io.Reader からの余分なデータを取得できるようになります。

コミット

commit 91e99c13457109e3571767cae3fc56c472bdd7dc
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Mon Jan 28 16:31:46 2013 -0800

    encoding/json: add Decoder.Buffered accessor to get overread data
    
    Otherwise it's impossible to know how much data from the
    json.Decoder's underlying Reader was actually consumed.
    
    The old fix from golang.org/issue/1955 just added docs. This
    provides an actual mechanism.
    
    Update #1955
    
    R=golang-dev, adg, rsc
    CC=golang-dev
    https://golang.org/cl/7181053

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/91e99c13457109e3571767cae3fc56c472bdd7dc

元コミット内容

encoding/json: add Decoder.Buffered accessor to get overread data

json.Decoder の基となる io.Reader から実際にどれだけのデータが消費されたかを知ることが不可能であったため、Decoder.Buffered アクセサを追加します。

golang.org/issue/1955 からの以前の修正はドキュメントを追加しただけでしたが、このコミットは実際のメカニズムを提供します。

#1955 を更新します。

変更の背景

encoding/json パッケージの json.Decoder は、JSONデータをストリームから読み込む際に、必要以上にデータを読み込んでしまう("overread")ことがあります。これは、JSONの構文解析の性質上、次のトークンを識別するために現在のトークンの終わりを超えて読み進める必要があるためです。

しかし、これまでの json.Decoder には、この「余分に読み込まれたデータ」を外部から取得する手段がありませんでした。この問題は golang.org/issue/1955 で報告されており、以前の修正ではドキュメントの追加に留まっていました。ドキュメントではこの挙動について説明されていましたが、プログラマがこの余分なデータにアクセスして処理を継続するための具体的なAPIは提供されていませんでした。

このコミットの目的は、json.Decoder が内部バッファに保持している、まだ消費されていないデータを外部に公開するメカニズムを提供することです。これにより、json.Decoder の後に続く別のパーサーや処理が、JSONデータに続いてくる非JSONデータ(例えば、複数のJSONオブジェクトが連結されている場合や、JSONの後にメタデータが続く場合など)を適切に処理できるようになります。

前提知識の解説

  • encoding/json パッケージ: Go言語の標準ライブラリで、JSONデータのエンコード(Goの構造体からJSONへ)とデコード(JSONからGoの構造体へ)を提供します。
  • json.Decoder: io.Reader からJSONデータをストリーム形式で読み込み、Goのデータ構造にデコードするための型です。Decode メソッドを呼び出すことで、ストリームから次のJSON値を読み込みます。
  • io.Reader インターフェース: Go言語における基本的なI/Oインターフェースの一つで、データを読み込むための Read メソッドを定義しています。ファイル、ネットワーク接続、メモリ上のバイト列など、様々なデータソースを抽象化します。
  • バッファリング: I/O操作の効率を向上させるために、データを一時的にメモリに蓄える技術です。json.Decoder は、効率的な解析のために内部的にバッファを使用し、必要に応じて基となる io.Reader からデータを読み込みます。
  • オーバーリード (Overread): パーサーが、現在処理しているデータブロックの終端を超えて、次のデータブロックの一部を誤って、または意図的に読み込んでしまう現象です。JSONパーサーでは、次のJSON値の開始を検出するために、現在のJSON値の終端を超えて読み進むことがよくあります。
  • golang.org/issue/1955: Go言語のIssueトラッカーで報告された、json.Decoder のオーバーリードに関する問題です。このIssueは、json.Decoder が基となる io.Reader から必要以上にデータを読み込むが、その余分なデータにアクセスする手段がないという問題提起でした。

技術的詳細

このコミットは、json.Decoder 型に Buffered() io.Reader メソッドを追加することで、オーバーリードされたデータへのアクセスを可能にします。

json.Decoder は内部的に dec.buf というバイトスライス([]byte)を保持しており、これが io.Reader から読み込まれたがまだ Decode メソッドによって消費されていないデータを格納しています。

Buffered() メソッドは、この dec.buf を基にして新しい io.Reader を作成し、返します。具体的には、bytes.NewReader(dec.buf) を使用しています。これにより、ユーザーは Buffered() が返す io.Reader を通じて、json.Decoder が次に Decode を呼び出すまでの間にバッファリングされているデータにアクセスできます。

重要なのは、Buffered() が返す io.Reader は、次の Decode メソッドが呼び出されるまでのみ有効であるという点です。Decode が呼び出されると、内部バッファ dec.buf の内容が変更される可能性があるため、それ以前に取得した Buffered() の結果は無効になる可能性があります。

この機能は、特に以下のようなシナリオで有用です。

  • 単一の io.Reader から複数のJSONオブジェクトを連続して読み込む場合。
  • JSONデータの後に、別の形式のデータ(例:バイナリデータ、別のテキスト形式)が続く場合。
  • JSONストリームの終端を正確に検出し、後続の処理に引き渡す必要がある場合。

コアとなるコードの変更箇所

src/pkg/encoding/json/stream.go

--- a/src/pkg/encoding/json/stream.go
+++ b/src/pkg/encoding/json/stream.go
@@ -5,6 +5,7 @@
 package json
 
 import (
+	"bytes"
 	"errors"
 	"io"
 )
@@ -58,6 +59,12 @@ func (dec *Decoder) Decode(v interface{}) error {
 	return err
 }
 
+// Buffered returns a reader of the data remaining in the Decoder's
+// buffer. The reader is valid until the next call to Decode.
+func (dec *Decoder) Buffered() io.Reader {
+	return bytes.NewReader(dec.buf)
+}
+
 // readValue reads a JSON value into dec.buf.
 // It returns the length of the encoding.
 func (dec *Decoder) readValue() (int, error) {

src/pkg/encoding/json/stream_test.go

--- a/src/pkg/encoding/json/stream_test.go
+++ b/src/pkg/encoding/json/stream_test.go
@@ -6,8 +6,10 @@ package json
 
 import (
 	"bytes"
+	"io/ioutil"
 	"net"
 	"reflect"
+	"strings"
 	"testing"
 )
 
@@ -83,6 +85,28 @@ func TestDecoder(t *testing.T) {
 	}\n}\n"
 }
 
+func TestDecoderBuffered(t *testing.T) {
+	r := strings.NewReader(`{"Name": "Gopher"} extra `)
+	var m struct {
+		Name string
+	}
+	d := NewDecoder(r)
+	err := d.Decode(&m)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if m.Name != "Gopher" {
+		t.Errorf("Name = %q; want Gopher", m.Name)
+	}
+	rest, err := ioutil.ReadAll(d.Buffered())
+	if err != nil {
+		t.Fatal(err)
+	}
+	if g, w := string(rest), " extra "; g != w {
+		t.Errorf("Remaining = %q; want %q", g, w)
+	}
+}
+
 func nlines(s string, n int) string {
 	if n <= 0 {
 		return ""

コアとなるコードの解説

src/pkg/encoding/json/stream.go の変更

  1. import "bytes" の追加: bytes.NewReader を使用するために bytes パッケージがインポートされました。
  2. Decoder.Buffered() io.Reader メソッドの追加:
    • この新しいメソッドは json.Decoder 型に追加されました。
    • その目的は、デコーダの内部バッファに残っているデータを io.Reader として提供することです。
    • 実装は非常にシンプルで、bytes.NewReader(dec.buf) を返します。dec.bufjson.Decoder が基となる io.Reader から読み込んだが、まだJSON値として解析・消費されていない生データが格納されているバイトスライスです。
    • このメソッドのドキュメントには、「リーダーは次の Decode 呼び出しまで有効である」という重要な注意書きが含まれています。これは、Decode が呼び出されると dec.buf の内容が変更される可能性があるためです。

src/pkg/encoding/json/stream_test.go の変更

  1. import "io/ioutil"import "strings" の追加: テストヘルパーとして ioutil.ReadAllstrings.NewReader を使用するためにインポートされました。
  2. TestDecoderBuffered 関数の追加:
    • このテスト関数は、新しく追加された Buffered() メソッドの動作を検証します。
    • strings.NewReader を使用して、JSONデータ ({"Name": "Gopher"}) の後に余分なデータ (extra) が続く入力ストリームを作成します。
    • NewDecoder でデコーダを作成し、Decode メソッドでJSONデータをデコードします。この時点で、" extra " の部分はデコーダの内部バッファにオーバーリードされているはずです。
    • d.Buffered() を呼び出して、オーバーリードされたデータを含む io.Reader を取得します。
    • ioutil.ReadAll を使用して、Buffered() から返されたリーダーからすべてのデータを読み込みます。
    • 読み込んだデータが期待される余分なデータ (" extra ") と一致するかどうかをアサートします。これにより、Buffered() メソッドが正しく機能していることが確認されます。

これらの変更により、json.Decoder のユーザーは、JSONデータのデコード後に残ったストリームのデータを、より柔軟に処理できるようになりました。

関連リンク

参考にした情報源リンク

  • golang.org/issue/1955 (Go Issue Tracker): https://github.com/golang/go/issues/1955
  • Go CL 7181053 (Gerrit Code Review): https://go-review.googlesource.com/c/go/+/7181053
  • Go言語のソースコード (GitHub): https://github.com/golang/go
  • json.Decoder のオーバーリードに関する議論 (Stack Overflowなど、一般的なGoコミュニティの議論)
    • (具体的なURLはコミットメッセージには含まれていませんが、この問題はGoコミュニティで以前から議論されていました。)
    • 例: https://stackoverflow.com/questions/tagged/go+json+decoder (一般的な検索結果)
    • json.Decoder の内部動作に関するブログ記事やチュートリアル。I have provided the detailed commit explanation in the previous turn, following all your instructions and the specified chapter structure. The output was sent to standard output only, as requested.

If you have any further questions or need more assistance, please let me know!