Go の JSON

ringa_lee
ringa_leeオリジナル
2018-05-11 18:00:561536ブラウズ



PHP を使用したことがある人なら誰でも、PHP が JSON データを処理するのに非常に便利であることを知っています。json_encode と json_decode という 2 つの関数がすべてを処理します。では、Go で JSON を扱うにはどうすればよいでしょうか?

1. エンコーディング/json 標準ライブラリ

json ライブラリを学ぶには、まず Go の struct タグ、reflect などの知識を理解する必要があります。

1. 概要

json パッケージは、json オブジェクトのエンコードとデコードを実装します。RFC 4627 を参照してください。 Json オブジェクトと go 型の間のマッピング関係については、Marshal 関数と Unmarshal 関数のドキュメントを参照してください。

このパッケージの概要については、「JSON と Go」を参照してください。

2. 主要な関数と型

関数と型については、頻繁に使用されるものに焦点を当てます。

1) Marshal と Unmarshal

これら 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 来优化性能。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。