PHP を使用したことがある人なら誰でも、PHP が JSON データを処理するのに非常に便利であることを知っています。json_encode と json_decode という 2 つの関数がすべてを処理します。では、Go で JSON を扱うにはどうすればよいでしょうか?
json ライブラリを学ぶには、まず Go の struct タグ、reflect などの知識を理解する必要があります。
json パッケージは、json オブジェクトのエンコードとデコードを実装します。RFC 4627 を参照してください。 Json オブジェクトと go 型の間のマッピング関係については、Marshal 関数と Unmarshal 関数のドキュメントを参照してください。
このパッケージの概要については、「JSON と Go」を参照してください。
関数と型については、頻繁に使用されるものに焦点を当てます。
これら 2 つは、最も一般的に使用される関数であり、json オブジェクトのエンコードとデコードです。これら 2 つの関数のドキュメントは非常に長く、Go 型と json オブジェクトの間のマッピング関係が詳細に説明されています。マッピング関係は次のように構成されます:
bool、JSON ブール値の場合
float64、JSON 数値の場合
string、JSON 文字列の場合
[]interface {}、JSON 配列の場合
map[string]interface{}、JSON オブジェクトの場合
JSON null の場合 nil
詳細なエンコードとデコードのルールについては、「
①デフォルトでは、上記のマッピングに従って解析されます。
②オブジェクトが json.Marshaler/Unmarshaler インターフェイスを実装している場合、そうでない場合。 nil ポインターの場合、対応するメソッドがエンコードとデコードに使用されます。インターフェースが実装されていないが、encoding.TextMarshaler/TextUnmarshaler インターフェースが実装されている場合は、エンコードとデコードにインターフェースの対応するメソッドが呼び出されます。 >
③構造体は、関連するエンコードとデコードを制御するために「json」タグを使用します。④構造体の匿名フィールドはデフォルトで展開されます。展開されないようにタグを指定できます。 ; ⑤匿名フィールドがある場合、同じレベル名に同じフィールドが存在する場合、競合は発生せず、特定の処理ルール文書には ⑥ をデコードするとき。構造体、冗長フィールド、または存在しないフィールド (エクスポートされていないフィールドを含む) は、エラーを報告せずに無視されます。また、Unmarshal に渡される 2 番目のパラメーターはポインターである必要があることに注意してください。 使用例:
package mainimport ( "encoding/json" "fmt")func main() { type Book struct { Name string Price float64 // `json:"price,string"` } var person = struct { Name string Age int Book }{ Name: "polaris", Age: 30, Book: Book{ Price: 3.4, Name: "Go语言", }, } buf, _ := json.Marshal(person) fmt.Println(string(buf)) // Output:{"Name":"polaris","Age":30,"Price":3.4} // Book 中的 Name 被忽略了}インライン型を展開したくない場合は、次のタグを追加してください:
var person = struct { Name string Age int Book `json:"Book"`}場合によっては、PHP など以前 (弱い Type 言語)、Age の値は "Age": "30" になる可能性があります。この形式は互換性のために Go に実装されるか、Price などの浮動小数点値がクライアントに返される可能性があります。精度の問題がある場合、クライアントはそれを表示し、浮動小数点値の文字列を返すだけです。この状況では、タグ「json:”,string”」を追加するだけで済みます。ここでのカンマの後の「文字列」はタグのオプションです。 特定のフィールドを無視したい場合は、`json:”-”` を追加します。値が空の場合に無視したい場合は、`json:”,omitempty” のように、omitempty オプションを追加します。 `デコード時には、エクスポートされた構造体フィールドのタグとの一致が優先され、次にフィールド、最後に大文字と小文字を区別しないさまざまな形式のフィールド (NAME/NAme などと一致する名前など) が優先されます。 。 2) MarshalIndent 関数 この関数の機能は Marshal と同じですが、人間が読みやすいように json をフォーマットするだけです。この関数が上記の例で使用された場合、MarshalIndent(person, “”, “t”) の出力は次のようになります:
{ "Name": "polaris", "Age": 30, "Price": 3.4}3) エンコーダーとデコーダー場合によっては、 Request などから開始することもできます。解析のために入力ストリームから json を直接読み取るか、エンコードされた json を直接出力します。便宜上、標準ライブラリには Decoder タイプと Encoder タイプが用意されています。これらはそれぞれ io.Reader および io.Writer を通じてインスタンス化され、それらからデータを読み書きします。 ソース コードを読むと、Encoder.Encode/Decoder.Decode と Marshal/Unmarshal の実装がほぼ同じであることがわかります。いくつかの違いがあります。Decoder にはメソッド UseNumber があり、その関数は次のとおりです。デフォルトでは、json の数が Go で float64 にマッピングされます。これにより、次のような問題が発生することがあります。
b := []byte(`{"Name":"polaris","Age":30,"Money":20.3}`)var person = make(map[string]interface{})err := json.Unmarshal(b, &person)if err != nil { log.Fatalln("json unmarshal error:", err)}age := person["Age"]log.Println(age.(int))age が int であることを望みますが、結果はパニックになります。
Decoder.Decode に変更しました (UseNumber を使用) 試してみます:
b := []byte(`{"Name":"polaris","Age":30,"Money":20.3}`)var person = make(map[string]interface{})decoder := json.NewDecoder(bytes.NewReader(b))decoder.UseNumber()err := decoder.Decode(&person)if err != nil { log.Fatalln("json unmarshal error:", err)}age := person["Age"]log.Println(age.(json.Number).Int64())json.Number 型を使用しました。 4) RawMessage 型 この型の定義は、RawMessage []byte 型です。元の json オブジェクトが保存されており、遅延する可能性があることがわかります。 json のデコード。使用例については、http://docs.studygolang.com/pkg/encoding/json/#RawMessage の例を参照してください。 5) 他の関数やタイプ (エラー タイプなど) は一般的に使用されないため、ここでは説明しません。公式ドキュメントを直接参照してください。 2. 実際のアプリケーションの問題 クライアントとサーバーが json データ形式を使用して通信する場合、一方ではクライアントの json データをデコードする必要があります。 jsonをエンコードしてクライアントに送信します。
一般的,服务器发送给客户端的 json 数据,是通过 struct、[]struct 或 map[string]interface{} 等编码得到。这里除了上文说到的,可能需要对数值类型使用 string tag options 之外,对于 time.Time 类型(实现了Marshaler 接口),默认编码得到的时间格式是:RFC3339即2006-01-02T15:04:05Z07:00,很多时候客户端可能不希望得到这样的时间,他们更多时候只是需要一个可读的时间字符串,如 2006-01-02 15:04:05。对此,我们可以定义自己的类型 type OftenTime time:
func (self OftenTime) MarshalJSON() ([]byte, error) { t := time.Time(self) if y := t.Year(); y < 0 || y >= 10000 { return nil, errors.New("Time.MarshalJSON: year outside of range [0,9999]") } return []byte(t.Format(`"2006-01-02 15:04:05"`)), nil}func (this *OftenTime) UnmarshalJSON(data []byte) (err error) { t := time.Time(*this) return t.UnmarshalJSON(data)}
另外,有一个坑,json 对象的 key 必须是字符串,所以 map[int]interface{} 在编码时会报错,错误是 json.UnsupportedTypeError.
对于接收客户端数据,进行 json 解码,遇到的问题可能比较多,特别是同时接收多种语言的数据,比如 PHP、Java 等。比如 b := []byte(`{“Name”:”polaris”,”Age”:30,”Money”:20.3}`),PHP 传递过来的可能是:b := []byte(`{“Name”:”polaris”,”Age”:”30″,”Money”:”20.3″}`),在使用 struct 接收数据时,对于 Age,如果是 int,我们可以直接定义为 int 类型,但如果是string,可以通过 string tag options 接收;但如果Age有时是 int, 有时是 string,就会出问题。最理想的情况,当然是不希望出现这种情况,但有一点,程序要保证出现这种情况时,不能 panic。
在实际应用中,我就遇到了上面的问题,于是,自己写了一个 json 解析,能支持自动类型转换。代码开源在 github: https://github.com/polaris1119/jsonutils
很明显,json 的编解码,使用了 Go 的反射功能,所以,性能自然不是太好,正因为如此,有了 ffjson、easyjson 之类的开源库(在 github 上),它们的原理是通过 go generate 根据 struct 生成相应的代码,避免反射。如果你对性能要求比较高,但又不想使用msgpack/pb/thrift 之类的,那么可以考虑使用 ffjson/easyjson 来优化性能。