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"`}
{ "Name": "polaris", "Age": 30, "Price": 3.4}
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))
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 数据,是通过 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 来优化性能。