【譯文】原文地址
Go最令人沮喪的事之一是它在編碼JSON的時候如何對nil切片的處理胰柑。其并不是返回我們期望的空數(shù)組福澡,而是返回Null批钠,如下代碼所示:
package main
import (
"encoding/json"
"fmt"
)
// Bag holds items
type Bag struct {
Items []string
}
// PrintJSON converts payload to JSON and prints it
func PrintJSON(payload interface{}) {
response, _ := json.Marshal(payload)
fmt.Printf("%s\n", response)
}
func main() {
bag1 := Bag{}
PrintJSON(bag1)
}
Outputs:
{"Items":null}
這是根據(jù)json包對nil切片處理方式?jīng)Q定的:
數(shù)組和切片值編碼為JSON數(shù)組,除了[]byte編碼為base64字符串变隔,nil切片編碼為null JSON值。
有一些建議可以修改json包來處理nil切片:
- encoding/json nilasempty將nil-slice編碼為[]
- encoding/json: "nonil"結(jié)構(gòu)體標(biāo)簽將空切片映射為non-null
目前這些建議還沒有在json包中實現(xiàn)。因此,為了解決null數(shù)組的問題,必須將nil切片設(shè)置為空切片碧囊。看如下修改:
package main
import (
"encoding/json"
"fmt"
)
// Bag holds items
type Bag struct {
Items []string
}
// PrintJSON converts payload to JSON and prints it
func PrintJSON(payload interface{}) {
response, _ := json.Marshal(payload)
fmt.Printf("%s\n", response)
}
func main() {
bag1 := Bag{}
bag1.Items = make([]string, 0)
PrintJSON(bag1)
}
輸出更改為:
{"Items":[]}
然而纤怒,在任何可能存在nil切片的地方都要這樣設(shè)置為空切片是很繁瑣的糯而。有沒有更好的方法?
方法1:自定義Marshaler
根據(jù)Go json文檔:
Marshal遞歸地遍歷值v泊窘。如果遇到的值實現(xiàn)了Marshaler接口并且不是空指針熄驼,Marshal函數(shù)會調(diào)用對應(yīng)類型的MarshalJSON方法來編碼JSON像寒。
因此,如果我們實現(xiàn)了Marshaler接口:
// Marshaler is the interface implemented by types that
// can marshal themselves into valid JSON.
type Marshaler interface {
MarshalJSON() ([]byte, error)
}
對象在編碼成JSON的時候瓜贾,自定義的MarshalJSON方法會被調(diào)用诺祸。看如下修改:
package main
import (
"encoding/json"
"fmt"
)
// Bag holds items
type Bag struct {
Items []string
}
// MarshalJSON initializes nil slices and then marshals the bag to JSON
func (b Bag) MarshalJSON() ([]byte, error) {
type Alias Bag
a := struct {
Alias
}{
Alias: (Alias)(b),
}
if a.Items == nil {
a.Items = make([]string, 0)
}
return json.Marshal(a)
}
// PrintJSON converts payload to JSON and prints it
func PrintJSON(payload interface{}) {
response, _ := json.Marshal(payload)
fmt.Printf("%s\n", response)
}
func main() {
bag1 := Bag{}
PrintJSON(bag1)
}
Outputs:
{"Items":[]}
代碼中Alias類型別名是必須的祭芦,為了防止調(diào)用json.Marshal時候陷入無限循環(huán)筷笨。
方法2:動態(tài)初始化
另一種處理nil切片的方法是使用reflect包動態(tài)地檢查結(jié)構(gòu)體中的每個字段,如果是nil切片就用空切片來替換龟劲。如下所示:
package main
import (
"encoding/json"
"fmt"
"reflect"
)
// Bag holds items
type Bag struct {
Items []string
}
// NilSliceToEmptySlice recursively sets nil slices to empty slices
func NilSliceToEmptySlice(inter interface{}) interface{} {
// original input that can't be modified
val := reflect.ValueOf(inter)
switch val.Kind() {
case reflect.Slice:
newSlice := reflect.MakeSlice(val.Type(), 0, val.Len())
if !val.IsZero() {
// iterate over each element in slice
for j := 0; j < val.Len(); j++ {
item := val.Index(j)
var newItem reflect.Value
switch item.Kind() {
case reflect.Struct:
// recursively handle nested struct
newItem = reflect.Indirect(reflect.ValueOf(NilSliceToEmptySlice(item.Interface())))
default:
newItem = item
}
newSlice = reflect.Append(newSlice, newItem)
}
}
return newSlice.Interface()
case reflect.Struct:
// new struct that will be returned
newStruct := reflect.New(reflect.TypeOf(inter))
newVal := newStruct.Elem()
// iterate over input's fields
for i := 0; i < val.NumField(); i++ {
newValField := newVal.Field(i)
valField := val.Field(i)
switch valField.Kind() {
case reflect.Slice:
// recursively handle nested slice
newValField.Set(reflect.Indirect(reflect.ValueOf(NilSliceToEmptySlice(valField.Interface()))))
case reflect.Struct:
// recursively handle nested struct
newValField.Set(reflect.Indirect(reflect.ValueOf(NilSliceToEmptySlice(valField.Interface()))))
default:
newValField.Set(valField)
}
}
return newStruct.Interface()
case reflect.Map:
// new map to be returned
newMap := reflect.MakeMap(reflect.TypeOf(inter))
// iterate over every key value pair in input map
iter := val.MapRange()
for iter.Next() {
k := iter.Key()
v := iter.Value()
// recursively handle nested value
newV := reflect.Indirect(reflect.ValueOf(NilSliceToEmptySlice(v.Interface())))
newMap.SetMapIndex(k, newV)
}
return newMap.Interface()
case reflect.Ptr:
// dereference pointer
return NilSliceToEmptySlice(val.Elem().Interface())
default:
return inter
}
}
// PrintJSON converts payload to JSON and prints it
func PrintJSON(payload interface{}) {
newPayload := NilSliceToEmptySlice(payload)
response, _ := json.Marshal(newPayload)
fmt.Printf("%s\n", response)
}
func main() {
bag1 := Bag{}
PrintJSON(bag1)
}
Output:
{"Items":[]}
總結(jié)
使用自定義Marshaler的缺點(diǎn)是必須為每個包含切片的結(jié)構(gòu)體實現(xiàn)Marshaler接口胃夏。動態(tài)初始化方式肯定會慢一些,因為需要對結(jié)構(gòu)體的每個字段都進(jìn)行檢查昌跌。然而這種方法仰禀,在有許多帶切片的結(jié)構(gòu)體,并且需要調(diào)用json.Marshal()方法的情況蚕愤,可以很好的工作答恶。