著者は、 Diversity in Tech Fund を選択して、 Write forDOnationsプログラムの一環として寄付を受け取りました。

序章

最新のプログラムでは、あるプログラムと別のプログラムの間で通信することが重要です。 ユーザーが別のプログラムにアクセスできるかどうかをチェックするGoプログラム、Webサイトに表示する過去の注文のリストを取得する JavaScript プログラム、または Rust [X183X ]プログラムはファイルからテスト結果を読み取ります。プログラムには、他のプログラムにデータを提供する方法が必要です。 ただし、多くのプログラミング言語には、他の言語が理解できない独自のデータを内部に格納する方法があります。 これらの言語が相互作用できるようにするには、データをすべての言語が理解できる共通の形式に変換する必要があります。 これらの形式の1つであるJSONは、インターネット上および同じシステム内のプログラム間でデータを送信するための一般的な方法です。

最新のプログラミング言語の多くには、標準ライブラリでJSONとの間でデータを変換する方法が含まれています。Goも同様です。 Goが提供するencoding/ json パッケージを使用することで、GoプログラムはJSONを使用して通信できる他のシステムとも対話できるようになります。

このチュートリアルでは、encoding/jsonパッケージを使用してmapからJSONデータにデータをエンコードするプログラムを作成し、次にstructタイプを使用するようにプログラムを更新します。代わりにデータをエンコードします。 その後、JSONデータをmapにデコードするようにプログラムを更新してから、最終的にJSONデータをstructタイプにデコードします。

前提条件

このチュートリアルに従うには、次のものが必要です。

  • Goバージョン1.16以降がインストールされています。これは、シリーズGoのローカルプログラミング環境をインストールおよびセットアップする方法に従って実行できます。
  • JSONの概要にあるJSONの知識。
  • Go構造体タグを使用してstructタイプのフィールドをカスタマイズする機能。 詳細については、Goで構造体タグを使用する方法を参照してください。
  • 必要に応じて、Goで日付と時刻の値を作成する方法に注意してください。 詳細については、Goで日付と時刻を使用する方法を参照してください。

マップを使用してJSONを生成する

JSONのエンコードとデコードに対するGoのサポートは、標準ライブラリのencoding/jsonパッケージによって提供されます。 そのパッケージから使用する最初の関数は、json.Marshal関数です。 マーシャリングは、シリアル化とも呼ばれ、メモリ内のプログラムデータを他の場所で送信または保存できる形式に変換するプロセスです。 次に、json.Marshal関数を使用して、GoデータをJSONデータに変換します。 json.Marshal関数は、JSONにマーシャリングする値としてinterface{}型を受け入れるため、任意の値をパラメーターとして渡すことができ、結果としてJSONデータを返します。 このセクションでは、json.Marshal関数を使用して、Go map値からさまざまなタイプのデータを含むJSONを生成し、それらの値を出力に出力するプログラムを作成します。

ほとんどのJSONはオブジェクトとして表され、stringキーとその他のさまざまなタイプが値として使用されます。 このため、GoでJSONデータを生成する最も柔軟な方法は、stringキーとinterface{}値を使用してデータをmapに配置することです。 stringキーはJSONオブジェクトキーに直接変換でき、interface{}値を使用すると、stringint、または別のmap[string]interface{}

プログラムでencoding/jsonパッケージの使用を開始するには、プログラム用のディレクトリが必要です。 このチュートリアルでは、projectsという名前のディレクトリを使用します。

まず、projectsディレクトリを作成し、次の場所に移動します。

  1. mkdir projects
  2. cd projects

次に、プロジェクトのディレクトリを作成します。 この場合、ディレクトリjsondataを使用します。

  1. mkdir jsondata
  2. cd jsondata

jsondataディレクトリ内で、nanoまたはお気に入りのエディタを使用して、main.goファイルを開きます。

  1. nano main.go

main.goファイルに、main関数を追加してプログラムを実行します。 次に、さまざまなキーとデータの種類を使用してmap[string]interface{}値を追加します。 次に、json.Marshal関数を使用して、mapデータをJSONデータにマーシャリングします。

main.goに次の行を追加します。

main.go
package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	data := map[string]interface{}{
		"intValue":    1234,
		"boolValue":   true,
		"stringValue": "hello!",
		"objectValue": map[string]interface{}{
			"arrayValue": []int{1, 2, 3, 4},
		},
	}

	jsonData, err := json.Marshal(data)
	if err != nil {
		fmt.Printf("could not marshal json: %s\n", err)
		return
	}

	fmt.Printf("json data: %s\n", jsonData)
}

data変数には、各値のキーとしてstringが含まれていることがわかりますが、これらのキーの値は異なります。 1つはint値、もう1つはbool値、もう1つは[]int値を含む別のmap[string]interface{}値です。

data変数をjson.Marshalに渡すと、関数は指定されたすべての値を調べて、それらがどのタイプであり、JSONでそれらをどのように表現するかを決定します。 変換に問題がある場合、json.Marshal関数は問題を説明するerrorを返します。 ただし、変換が成功した場合、jsonData変数にはマーシャリングされたJSONデータの[]byteが含まれます。 []byte値は、myString := string(jsonData)、またはフォーマット文字列の%s 動詞を使用して、string値に変換できるため、次に、fmt.Printfを使用してJSONデータを画面に出力できます。

ファイルを保存して閉じます。

プログラムの出力を確認するには、go runコマンドを使用して、main.goファイルを提供します。

  1. go run main.go

出力は次のようになります。

Output
json data: {"boolValue":true,"intValue":1234,"objectValue":{"arrayValue":[1,2,3,4]},"stringValue":"hello!"}

出力では、最上位のJSON値が、それを囲む中括弧({})で表されるオブジェクトであることがわかります。 dataに含めたすべての値が存在します。 また、objectValuemap[string]interface{}{}で囲まれた別のJSONオブジェクトに変換され、配列値とともにarrayValueが含まれていることもわかります。 [1,2,3,4]の。

JSONでのエンコード時間

ただし、encoding/jsonパッケージは、stringintの値などのタイプをサポートするだけではありません。 また、より複雑なタイプをエンコードすることもできます。 サポートされているより複雑なタイプの1つは、timeパッケージのtime.Timeタイプです。

注:Goのtimeパッケージの詳細については、チュートリアルGoで日付と時刻を使用する方法を確認してください。

これが実際に動作することを確認するには、main.goファイルを再度開き、time.Date関数を使用してtime.Time値をデータに追加します。

main.go
package main

import (
	"encoding/json"
	"fmt"
	"time"
)

func main() {
	data := map[string]interface{}{
		"intValue":    1234,
		"boolValue":   true,
		"stringValue": "hello!",
		"dateValue":   time.Date(2022, 3, 2, 9, 10, 0, 0, time.UTC),
		"objectValue": map[string]interface{}{
			"arrayValue": []int{1, 2, 3, 4},
		},
	}

	...
}

この更新により、UTCタイムゾーンの日付March 2, 2022,と時刻9:10:00 AMdateValueキーに割り当てられます。

変更を保存したら、前と同じgo runコマンドを使用してプログラムを再実行します。

  1. go run main.go

出力は次のようになります。

Output
json data: {"boolValue":true,"dateValue":"2022-03-02T09:10:00Z","intValue":1234,"objectValue":{"arrayValue":[1,2,3,4]},"stringValue":"hello!"}

今回の出力では、JSONデータにdateValueフィールドが表示され、時刻は RFC 3339 形式でフォーマットされています。これは、日付と時刻をstring値。

null値をJSONでエンコードする

プログラムが対話するシステムによっては、JSONデータでnull値を送信する必要がある場合があり、Goのencoding/jsonパッケージでもそれを処理できます。 mapを使用すると、nil値を持つ新しいstringキーを追加するだけです。

いくつかのnull値をJSON出力に追加するには、main.goファイルを再度開き、次の行を追加します。

main.go
...

func main() {
	data := map[string]interface{}{
		"intValue":    1234,
		"boolValue":   true,
		"stringValue": "hello!",
    "dateValue":   time.Date(2022, 3, 2, 9, 10, 0, 0, time.UTC),
		"objectValue": map[string]interface{}{
			"arrayValue": []int{1, 2, 3, 4},
		},
		"nullStringValue": nil,
		"nullIntValue":    nil,
	}

	...
}

データに追加した値には、string値またはint値であるというキーがありますが、実際には、これらの値のいずれかを作成しているコードはありません。 mapにはinterface{}の値があるため、コードはinterface{}の値がnilであることを知っています。 このmapを使用してGoデータからJSONデータに変換するだけなので、この時点での区別は違いはありません。

main.goに変更を保存したら、go runを使用してプログラムを実行します。

  1. go run main.go

出力は次のようになります。

Output
json data: {"boolValue":true,"dateValue":"2022-03-02T09:10:00Z","intValue":1234,"nullIntValue":null,"nullStringValue":null,"objectValue":{"arrayValue":[1,2,3,4]},"stringValue":"hello!"}

出力には、nullIntValueフィールドとnullStringValueフィールドがJSONnull値に含まれていることがわかります。 このようにして、map[string]interface{}値を使用して、Goデータを期待されるフィールドを持つJSONデータに変換できます。

このセクションでは、map[string]interface{}値をJSONデータにマーシャリングできるプログラムを作成しました。 次に、time.Timeフィールドをデータに追加し、null値フィールドのペアも含めました。

map[string]interface{}を使用してJSONデータをマーシャリングすることは非常に柔軟ですが、同じデータを複数の場所に送信する必要がある場合は面倒になる可能性もあります。 このデータをコード内の複数の場所にコピーすると、誤ってフィールド名を誤って入力したり、誤ったデータをフィールドに割り当てたりする可能性があります。 このような場合は、structタイプを使用して、JSONに変換するデータを表すと便利な場合があります。

Structを使用してJSONを生成する

Goのような静的に型付けされた言語を使用する利点の1つは、これらの型を使用して、コンパイラーにプログラムの整合性をチェックまたは強制させることができることです。 Goのencoding/jsonパッケージでは、JSONデータを表すstructタイプを定義することで、これを利用できます。 structに含まれるデータが、構造体タグを使用してどのように変換されるかを制御できます。 このセクションでは、mapタイプの代わりにstructタイプを使用してJSONデータを生成するようにプログラムを更新します。

structを使用してJSONデータを定義する場合、翻訳されると予想されるフィールド名(structタイプ名自体ではない)をエクスポートする必要があります。つまり、大文字で始まる必要があります。 IntValue、またはencoding/jsonパッケージは、フィールドにアクセスしてJSONに変換することができなくなります。 これらのフィールドの名前を制御するために構造体タグを使用しない場合、フィールド名はstructの場合と同じように直接変換されます。 データの形成方法によっては、デフォルトの名前を使用することがJSONデータで希望する場合があります。 この場合、構造体タグを追加する必要はありません。 ただし、多くのJSONコンシューマーは、フィールド名にintValueint_valueなどの名前形式を使用するため、これらの構造体タグを追加すると、その変換の実行方法を制御できます。

たとえば、JSONにマーシャリングしたIntValueというフィールドを持つstructがあるとします。

type myInt struct {
	IntValue int
}

data := &myInt{IntValue: 1234}

json.Marshal関数を使用してdata変数をJSONにマーシャリングした場合、次の値になります。

{"IntValue":1234}

ただし、JSONコンシューマーが、フィールドの名前がIntValueではなくintValueであることを期待している場合は、encoding/jsonを通知する方法が必要になります。 json.Marshalは、JSONデータでフィールドの名前が何であるかを認識していないため、フィールドに構造体タグを追加することで通知します。 json構造体タグをIntValueフィールドにintValueの値で追加することにより、json.MarshalintValueという名前を使用するように指示します。 JSONデータを生成する場合:

type myInt struct {
	IntValue int `json:"intValue"`
}

data := &myInt{IntValue: 1234}

今回、data変数をJSONにマーシャリングすると、json.Marshal関数はjson構造体タグを認識し、フィールドにintValueという名前を付けることができます。期待どおりの結果が得られます:

{"intValue":1234}

次に、JSONデータにstruct値を使用するようにプログラムを更新します。 myJSON structタイプを追加してトップレベルのJSONオブジェクトを定義し、myObjectstructを追加して内部JSONオブジェクトを定義します。 ObjectValueフィールド。 また、json構造体タグを各フィールドに追加して、json.MarshalにJSONデータでの名前の付け方を指示します。 また、data変数の割り当てを更新して、myJSON構造体を使用し、他のGostructと同様に宣言する必要があります。

main.goファイルを開き、次の変更を加えます。

main.go
...

type myJSON struct {
	IntValue        int       `json:"intValue"`
	BoolValue       bool      `json:"boolValue"`
	StringValue     string    `json:"stringValue"`
	DateValue       time.Time `json:"dateValue"`
	ObjectValue     *myObject `json:"objectValue"`
	NullStringValue *string   `json:"nullStringValue"`
	NullIntValue    *int      `json:"nullIntValue"`
}

type myObject struct {
	ArrayValue []int `json:"arrayValue"`
}

func main() {
	otherInt := 4321
	data := &myJSON{
		IntValue:    1234,
		BoolValue:   true,
		StringValue: "hello!",
		DateValue:   time.Date(2022, 3, 2, 9, 10, 0, 0, time.UTC),
		ObjectValue: &myObject{
			ArrayValue: []int{1, 2, 3, 4},
		},
		NullStringValue: nil,
		NullIntValue:    &otherInt,
	}

	...
}

これらの変更の多くは、以前のIntValueフィールド名の例に似ていますが、一部の変更は具体的に呼び出す必要があります。 それらの1つであるObjectValueフィールドは、*myObjectの参照型を使用して、JSONマーシャラーにmyObject値または[のいずれかへの参照を期待するように指示しています。 X160X]値。 これは、カスタムオブジェクトの複数のレイヤーであるJSONオブジェクトを定義する方法です。 JSONデータで必要な場合は、myObjectタイプ内で別のstructタイプを参照することもできます。 このパターンを使用すると、Gostructタイプを使用して非常に複雑なJSONオブジェクトを記述できます。

上記のコードで確認するもう1つのフィールドのペアは、NullStringValueNullIntValueです。 StringValueおよびIntValueとは異なり、これらの値のタイプは参照タイプ*stringおよび*intです。 デフォルトでは、stringおよびintタイプは、「空の」値が""および0であるため、nilの値を持つことはできません。 したがって、1つのタイプまたはnilのいずれかであるフィールドを表す場合は、それを参照にする必要があります。 たとえば、ユーザーアンケートがあり、ユーザーが質問に回答しないことを選択した場合(nullの値)を表現できるようにしたいとします。 ""値)。

このコードは、NullIntValueフィールドを更新して、4321の値を割り当て、*intなどの参照型に値を割り当てる方法を示します。 Goでは、変数を使用して、intstringなどのプリミティブ型への参照のみを作成できます。 したがって、NullIntValueフィールドに値を割り当てるには、最初に値を別の変数otherIntに割り当て、次に&otherIntを使用してその値への参照を取得します(代わりに&4321を直接実行すること)。

更新を保存したら、go runを使用してプログラムを実行します。

  1. go run main.go

出力は次のようになります。

Output
json data: {"intValue":1234,"boolValue":true,"stringValue":"hello!","dateValue":"2022-03-02T09:10:00Z","objectValue":{"arrayValue":[1,2,3,4]},"nullStringValue":null,"nullIntValue":4321}

この出力は、map[string]interface{}値を使用した場合と同じですが、今回はnullIntValueの値が4321であるため、[の値であることがわかります。 X155X]。

最初は、struct値の設定に余分な時間がかかる場合がありますが、一度定義すると、コードで何度も使用できるようになり、どこで使用しても結果は同じになります。彼ら。 mapが使用される可能性のあるすべての場所を検索する代わりに、それらを1つの場所で更新することもできます。

GoのJSONマーシャラーでは、値が空かどうかに基づいて、フィールドをJSON出力に含めるかどうかを制御することもできます。 大きなJSONオブジェクトや、常に含めたくないオプションのフィールドがある場合があるため、これらのフィールドを省略すると便利です。 フィールドが空のときに省略されるかどうかの制御は、json構造体タグのomitemptyオプションを介して行われます。

次に、プログラムを更新してNullStringValueフィールドをomitemptyにし、同じオプションでEmptyStringという新しいフィールドを追加します。

main.go
...

type myJSON struct {
	...
	
	NullStringValue *string   `json:"nullStringValue,omitempty"`
	NullIntValue    *int      `json:"nullIntValue"`
	EmptyString     string    `json:"emptyString,omitempty"`
}

...

これで、myJSONがマーシャリングされるときに、EmptyStringフィールドとNullStringValueフィールドの両方の値が空の場合、それらのフィールドは出力から除外されます。

変更を保存したら、go runを使用してプログラムを実行します。

  1. go run main.go

出力は次のようになります。

Output
json data: {"intValue":1234,"boolValue":true,"stringValue":"hello!","dateValue":"2022-03-02T09:10:00Z","objectValue":{"arrayValue":[1,2,3,4]},"nullIntValue":4321}

今回の出力では、nullStringValueフィールドが表示されなくなります。 nilの値を持つことで空と見なされるため、omitemptyオプションはそれを出力から除外しました。 また、新しいemptyStringフィールドも含まれていないことがわかります。 emptyStringの値はnilではありませんが、文字列のデフォルトの""値は空であると見なされるため、同様に除外されました。

このセクションでは、structタイプを使用して、mapタイプの代わりにjson.MarshalでJSONデータを生成するようにプログラムを更新しました。 また、JSON出力から空のフィールドを省略するようにプログラムを更新しました。

ただし、プログラムをJSONエコシステムにうまく適合させるには、JSONデータを生成するだけでは不十分です。 また、リクエストに応じて送信されるJSONデータ、またはリクエストを送信する他のシステムを読み取ることができる必要があります。 encoding/jsonパッケージは、JSONデータをさまざまなGoタイプにデコードする方法も提供します。 次のセクションでは、JSON文字列をGomapタイプにデコードするようにプログラムを更新します。

マップを使用したJSONの解析

このチュートリアルの最初のセクションで、JSONデータを生成するための柔軟な方法としてmap[string]interface{}を使用したのと同様に、JSONデータを読み取るための柔軟な方法としても使用できます。 json.Unmarshal関数は、本質的にjson.Marshal関数の反対であり、JSONデータを取得してGoデータに変換し直します。 json.UnmarshalにJSONデータと、マーシャリングされていないデータを入れるGo変数を指定すると、error値を返すか、[ X181X]成功した場合のエラー値。 このセクションでは、json.Unmarshal関数を使用して、事前定義されたstring値からmap変数にJSONデータを読み取るようにプログラムを更新します。 また、プログラムを更新して、Goデータを出力に出力します。

次に、json.Unmarshalを使用してJSONデータをmap[string]interface{}にアンマーシャリングするようにプログラムを更新します。 まず、元のdata変数をJSON文字列を含むjsonData変数に置き換えます。 次に、新しいdata変数をmap[string]interface{}として宣言して、JSONデータを受信します。 最後に、これらの変数でjson.Unmarshalを使用して、JSONデータにアクセスします。

main.goファイルを開き、main関数の行を次のように置き換えます。

main.go
...

func main() {
	jsonData := `
		{
			"intValue":1234,
			"boolValue":true,
			"stringValue":"hello!",
			"dateValue":"2022-03-02T09:10:00Z",
			"objectValue":{
				"arrayValue":[1,2,3,4]
			},
			"nullStringValue":null,
			"nullIntValue":null
		}
	`

	var data map[string]interface{}
	err := json.Unmarshal([]byte(jsonData), &data)
	if err != nil {
		fmt.Printf("could not unmarshal json: %s\n", err)
		return
	}

	fmt.Printf("json map: %v\n", data)
}

このアップデートでは、jsonData変数が生の文字列リテラルを使用して設定され、宣言が複数行にまたがって読みやすくなっています。 datamap[string]interface{}として宣言した後、jsonDatadatajson.Unmarshalに渡して、JSONデータを[にアンマーシャリングします。 X128X]変数。

jsonData変数は[]byteとしてjson.Unmarshalに渡されます。これは、関数が[]byteタイプを必要とし、jsonDataが最初にstringタイプ。 これが機能するのは、Goのstring[]byteに、またはその逆に変換できるためです。 data変数が参照として渡されているのは、json.Unmarshalがデータを変数に入れるために、変数がメモリ内のどこに格納されているかへの参照が必要だからです。

最後に、JSONデータがdata変数にアンマーシャリングされたら、fmt.Printfを使用して画面に出力します。

更新したプログラムを実行するには、変更を保存し、go runを使用してプログラムを実行します。

  1. go run main.go

出力は次のようになります。

Output
json map: map[boolValue:true dateValue:2022-03-02T09:10:00Z intValue:1234 nullIntValue:<nil> nullStringValue:<nil> objectValue:map[arrayValue:[1 2 3 4]] stringValue:hello!]

今回の出力は、JSON変換のGo側を示しています。 map値があり、JSONデータのさまざまなフィールドが含まれています。 JSONデータのnullフィールドもマップに表示されます。

Goデータはmap[string]interface{}にあるため、データの使用に必要な作業が少しあります。 目的のstringキー値を使用してmapから値を取得する必要があります。次に、受け取った値が[Xとして返されるため、期待した値であることを確認する必要があります。 X199X]