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

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

このコミットは、Go言語の標準ライブラリに初期のJSON(JavaScript Object Notation)ライブラリを追加するものです。Go言語がまだ公開される前の2008年12月に行われたもので、現在のencoding/jsonパッケージの原型となる機能が導入されています。このライブラリは、JSONデータのパース、Goのデータ構造へのアンマーシャリング、および汎用的なJSON表現の操作を提供します。

コミット

commit 793a6effcf58e2739e3053ad3199464e87eb5a58
Author: Russ Cox <rsc@golang.org>
Date:   Thu Dec 11 12:25:58 2008 -0800

    add JSON library
    
    R=r
    DELTA=1127  (1127 added, 0 deleted, 0 changed)
    OCL=20975
    CL=20983

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

https://github.com/golang/go/commit/793a6effcf58e2739e3053ad3199464e87eb5a58

元コミット内容

add JSON library

変更の背景

このコミットが行われた2008年当時、Go言語はまだ一般に公開されていませんでした。しかし、Webサービスや分散システムを構築する上で、JSONはデータ交換フォーマットとして既に広く普及していました。Go言語がこれらの用途で強力なツールとなるためには、JSONを効率的に扱うための組み込みサポートが不可欠でした。

このコミットは、Go言語の設計者の一人であるRuss Cox氏によって行われ、Goの標準ライブラリにJSONのパース、シリアライズ、デシリアライズ機能の基礎を築くことを目的としています。これにより、Goプログラムが外部システムとJSON形式でデータをやり取りする能力が提供され、Go言語の汎用性と実用性が向上しました。

前提知識の解説

JSON (JavaScript Object Notation)

JSONは、人間が読み書きしやすく、機械が解析しやすいデータ交換フォーマットです。JavaScriptのオブジェクトリテラルをベースにしており、キーと値のペアの集まり(オブジェクト)と、値の順序付きリスト(配列)という2つの基本的な構造でデータを表現します。Web APIや設定ファイルなどで広く利用されています。

Go言語の初期の設計思想

このコミットが行われた2008年時点のGo言語は、現在のGo言語とは異なる部分が多く存在します。特に、以下の点に注意が必要です。

  • exportキーワード: 当時のGo言語には、パッケージ外に公開する要素を示すためにexportキーワードが存在しました。これは後のバージョンで削除され、識別子が大文字で始まることで公開されるという現在のルールに変わりました。
  • arrayパッケージ: 現在のGoには組み込みの[]typeスライスがありますが、この時期にはarrayパッケージ(container/array)のような、より低レベルな配列操作を提供するパッケージが存在していた可能性があります。
  • reflectパッケージの初期段階: Goのreflectパッケージは、実行時に型情報を検査・操作するための強力な機能を提供します。このコミットでは、JSONデータをGoの構造体(struct)にマッピングするためにreflectパッケージが利用されていますが、そのAPIや機能は現在のものとは異なる可能性があります。
  • Makefileベースのビルドシステム: 当時のGoプロジェクトは、Makefileを使用してビルドプロセスを管理していました。これは、後のgo buildコマンドのような統合されたツールが登場する前の段階です。

データ構造のマーシャリングとアンマーシャリング

  • マーシャリング (Marshaling): プログラム内のデータ構造(例: Goの構造体)を、外部に保存または送信できる形式(例: JSON文字列)に変換するプロセスです。
  • アンマーシャリング (Unmarshaling): 外部形式のデータ(例: JSON文字列)を、プログラム内のデータ構造に変換するプロセスです。

このコミットでは、JSON文字列をGoの構造体にアンマーシャリングする機能が主に実装されています。

技術的詳細

このJSONライブラリは、主に以下の3つのGoファイルで構成されています。

  1. generic.go:

    • JSONの各データ型(文字列、数値、マップ、配列、真偽値、null)を表現するためのインターフェースJsonと、具体的な型(String, Number, Array, Bool, Map, Null)を定義しています。
    • これらの型は、JSONの階層構造をGoのインターフェースと構造体で表現するための基盤となります。
    • JsonToString関数は、Jsonインターフェースを実装する値をJSON文字列に変換します。
    • Walk関数は、JSONオブジェクトのパスを指定して要素にアクセスする機能を提供します。
    • Equal関数は、2つのJsonオブジェクトが等しいかどうかを比較します。
    • JsonBuilderは、JSONのパース時に動的にJsonオブジェクトを構築するためのヘルパー構造体です。
  2. parse.go:

    • JSON文字列をトークンに分割するLexer(字句解析器)を実装しています。Next()メソッドが次のトークンを読み込み、その種類と値を設定します。
    • JSONの文法に従ってトークンを解析し、Builderインターフェースを通じてデータ構造を構築するParser(構文解析器)を実装しています。
    • Unquote関数は、JSON文字列リテラルからエスケープシーケンスを解除します。
    • Quote関数は、Goの文字列をJSON文字列リテラルにエスケープします。
    • Parse関数は、JSON文字列とBuilderを受け取り、パースを実行します。
  3. struct.go:

    • Goのreflectパッケージを利用して、JSONデータをGoの構造体にアンマーシャリングする機能を提供します。
    • StructBuilderBuilderインターフェースを実装し、パースされたJSONの値をGoの構造体のフィールドにマッピングします。
    • Unmarshal関数は、JSON文字列とGoのインターフェース(通常は構造体へのポインタ)を受け取り、JSONデータをGoの構造体にデシリアライズします。
    • このファイルは、Goの初期のreflectパッケージの利用方法を示しており、型アサーション(例: v.(reflect.FloatValue))が多用されているのが特徴です。これは、当時のreflectパッケージのAPIが現在よりも低レベルであったことを示唆しています。

ビルドシステムへの統合

  • src/lib/Makefile: 新しいjsonライブラリをビルドシステムに組み込むために、DIRS変数にjsonを追加し、json.dirinstallターゲットの依存関係を定義しています。これにより、jsonパッケージがGoの標準ライブラリの一部としてビルドされるようになります。
  • src/lib/json/Makefile: jsonパッケージ自体のビルド方法を定義するMakefileです。Goのソースファイル(.go)をコンパイルし、アーカイブファイル(.a)を作成する手順が含まれています。gobuildというツールが自動生成したものであることがコメントから読み取れます。
  • src/run.bash: テスト実行スクリプトにlib/jsonを追加し、JSONライブラリのテストが自動的に実行されるようにしています。

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

このコミットでは、主に以下のファイルが新規追加されています。

  • src/lib/json/Makefile
  • src/lib/json/generic.go
  • src/lib/json/generic_test.go
  • src/lib/json/parse.go
  • src/lib/json/struct.go
  • src/lib/json/struct_test.go

既存ファイルへの変更は以下の通りです。

  • src/lib/Makefile:
    --- a/src/lib/Makefile
    +++ b/src/lib/Makefile
    @@ -12,6 +12,7 @@ DIRS=\
     	hash\
     	http\
     	io\
    +	json\
     	math\
     	net\
     	os\
    @@ -94,6 +95,8 @@ fmt.dirinstall: io.dirinstall reflect.dirinstall strconv.dirinstall
     hash.dirinstall: os.dirinstall
     http.dirinstall: bufio.install io.dirinstall net.dirinstall os.dirinstall strings.install
     io.dirinstall: os.dirinstall syscall.dirinstall
    +json.dirinstall: container/array.dirinstall fmt.dirinstall io.dirinstall math.dirinstall \
    +\tstrconv.dirinstall strings.install utf8.install
     net.dirinstall: fmt.dirinstall once.install os.dirinstall strconv.dirinstall
     os.dirinstall: syscall.dirinstall
     regexp.dirinstall: os.dirinstall
    
  • src/run.bash:
    --- a/src/run.bash
    +++ b/src/run.bash
    @@ -26,6 +26,7 @@ maketest() {
     maketest \
     	lib/fmt\
     	lib/hash\
    +	lib/json\
     	lib/math\
     	lib/reflect\
     	lib/regexp\
    

コアとなるコードの解説

src/lib/json/generic.go

このファイルは、JSONの汎用的な表現を定義しています。

export type Json interface {
	Kind() int;
	String() string;
	Number() float64;
	Bool() bool;
	Get(s string) Json; // For map access
	Elem(i int) Json;   // For array access
	Len() int;          // For array/map length
}

export const (
	StringKind = iota;
	NumberKind;
	MapKind;
	ArrayKind;
	BoolKind;
	NullKind;
)

Jsonインターフェースは、JSONのあらゆる値を抽象化します。Kind()メソッドでその型を識別し、String(), Number(), Bool()などで具体的な値を取得します。Get()はJSONオブジェクトのキーによるアクセス、Elem()はJSON配列のインデックスによるアクセス、Len()は配列やオブジェクトの長さを取得するために使われます。

JsonBuilderは、パース中にJSON構造を構築するための重要なコンポーネントです。

type JsonBuilder struct {
	ptr *Json; // Target for simple values
	a *array.Array; // Target for array elements
	i int; // Index for array elements
	m *map[string] Json; // Target for map keys
	k string; // Key for map elements
}

func (b *JsonBuilder) Put(j Json) {
	switch {
	case b.ptr != nil:
		*b.ptr = j;
	case b.a != nil:
		b.a.Set(b.i, j);
	case b.m != nil:
		b.m[b.k] = j;
	}
}

JsonBuilderは、パースされたJSONの値をどこに格納するかを管理します。Putメソッドは、現在のビルダが指す場所(ポインタ、配列の要素、マップのキー)にJSON値を設定します。ElemKeyメソッドは、ネストされた構造を構築するために新しいJsonBuilderインスタンスを返します。

src/lib/json/parse.go

このファイルは、JSONの字句解析(Lexer)と構文解析(Parser)を担当します。

type Lexer struct {
	s string; // Input string
	i int;    // Current position
	kind int; // Token kind
	token string; // Token value
}

func (t *Lexer) Next() {
	// Skips whitespace and identifies the next token (number, string, keyword, punctuation)
	// and updates t.kind and t.token.
}

Lexerは、入力JSON文字列を走査し、JSONの構成要素(数値、文字列、true, false, null、区切り文字など)を識別します。Next()メソッドが呼び出されるたびに、次の有効なトークンを読み込み、その種類と値をLexer構造体のフィールドに格納します。

export type Builder interface {
	// Set value methods
	Int64(i int64);
	Uint64(i uint64);
	Float64(f float64);
	String(s string);
	Bool(b bool);
	Null();
	Array();
	Map();

	// Create sub-Builders
	Elem(i int) Builder;
	Key(s string) Builder;
}

func ParseValue(lex *Lexer, build Builder) bool {
	// Parses a JSON value based on the current token from the lexer,
	// and uses the Builder interface to construct the corresponding Go value.
}

export func Parse(s string, build Builder) (ok bool, errindx int, errtok string) {
	// Initializes lexer and calls ParseValue to start parsing.
}

Builderインターフェースは、パースされたJSONの値をどのように構築するかを抽象化します。Int64, String, Array, Mapなどのメソッドは、対応するJSONの値をGoのデータ構造に変換する役割を担います。ParseValue関数は、Lexerからトークンを読み取り、Builderインターフェースのメソッドを呼び出すことで、JSONの階層構造を再構築します。Parse関数は、このプロセスを開始するエントリポイントです。

src/lib/json/struct.go

このファイルは、Goのreflectパッケージを使用して、JSONデータをGoの構造体にアンマーシャリングする機能を提供します。

type StructBuilder struct {
	val reflect.Value
}

func (b *StructBuilder) Int64(i int64) {
	// Sets the underlying reflect.Value to the integer value, handling type conversions.
}
// Similar methods for Uint64, Float64, Null, String, Bool

func (b *StructBuilder) Array() {
	// Initializes an array if the target reflect.Value is a pointer to an array.
}

func (b *StructBuilder) Elem(i int) Builder {
	// Returns a new StructBuilder for the i-th element of an array,
	// dynamically growing the array if necessary.
}

func (b *StructBuilder) Map() {
	// Initializes a map if the target reflect.Value is a pointer to a map.
}

func (b *StructBuilder) Key(k string) Builder {
	// Returns a new StructBuilder for the field named 'k' in a struct,
	// using reflection to find the field.
}

export func Unmarshal(s string, val interface{}) (ok bool, errtok string) {
	// Creates a StructBuilder from the provided interface{} (usually a pointer to a struct),
	// and then calls Parse to populate the struct.
}

StructBuilderは、Builderインターフェースを実装し、reflect.Valueを内部に保持します。これにより、パースされたJSONの値を、実行時に指定されたGoの構造体のフィールドに直接設定することができます。Elemメソッドは配列の要素に、Keyメソッドは構造体のフィールドにアクセスするための新しいStructBuilderを返します。Unmarshal関数は、JSON文字列をGoの構造体にデシリアライズするための主要なエントリポイントです。

関連リンク

  • Go言語の公式ウェブサイト: https://go.dev/
  • Go言語の初期の歴史に関する情報(Goのブログなど)

参考にした情報源リンク

  • JSONの公式ウェブサイト: https://www.json.org/json-ja.html
  • Go言語のencoding/jsonパッケージのドキュメント(現在のバージョン): https://pkg.go.dev/encoding/json
    • このコミットのコードとは異なりますが、現在のGoのJSON処理の標準的な方法を理解する上で参考になります。
  • Go言語のreflectパッケージのドキュメント(現在のバージョン): https://pkg.go.dev/reflect
    • このコミットのコードとは異なりますが、Goのリフレクションの概念を理解する上で参考になります。
  • Go言語の初期のコミット履歴(GitHubリポジトリ): https://github.com/golang/go/commits/master
    • このコミットがGoの歴史の中でどのような位置づけにあるかを理解する上で役立ちます。