面试题答案
一键面试package main
import (
"encoding/json"
"fmt"
"reflect"
)
type Address struct {
City string `json:"city"`
Country string `json:"country"`
}
type Company struct {
Name string `json:"name"`
Address Address `json:"address"`
}
type Employee struct {
Name string `json:"name"`
Age int `json:"age"`
Company Company `json:"company"`
}
func serializeToJSONWithReflect(obj interface{}) ([]byte, error) {
var result map[string]interface{}
value := reflect.ValueOf(obj)
if value.Kind() == reflect.Ptr {
value = value.Elem()
}
if value.Kind() != reflect.Struct {
return nil, fmt.Errorf("input must be a struct")
}
result = make(map[string]interface{})
typeOf := value.Type()
for i := 0; i < value.NumField(); i++ {
field := value.Field(i)
tag := typeOf.Field(i).Tag.Get("json")
if tag == "" {
continue
}
if field.Kind() == reflect.Struct {
subResult, err := serializeToJSONWithReflect(field.Interface())
if err != nil {
return nil, err
}
var subMap map[string]interface{}
json.Unmarshal(subResult, &subMap)
result[tag] = subMap
} else {
result[tag] = field.Interface()
}
}
return json.Marshal(result)
}
func main() {
emp := Employee{
Name: "John Doe",
Age: 30,
Company: Company{
Name: "Acme Inc",
Address: Address{
City: "New York",
Country: "USA",
},
},
}
data, err := serializeToJSONWithReflect(emp)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println(string(data))
}
- 优化思路:
- 减少反射调用次数,尽量一次遍历结构体的所有字段。
- 对结构体嵌套结构体的情况,递归处理,并且在递归处理时也遵循减少反射调用次数的原则。
- 代码解释:
serializeToJSONWithReflect
函数接受一个interface{}
类型的参数,首先检查参数是否为指针,如果是则获取指针指向的值。- 检查值是否为结构体类型,如果不是则返回错误。
- 初始化一个
map[string]interface{}
用于存储序列化后的结果。 - 遍历结构体的所有字段,获取字段的
json
tag,如果tag
为空则跳过。 - 如果字段是结构体类型,则递归调用
serializeToJSONWithReflect
进行序列化,并将反序列化后的map
作为结果存储到主map
中。 - 如果字段不是结构体类型,则直接将字段值存储到主
map
中。 - 最后将主
map
使用json.Marshal
转换为JSON格式的字节切片。
- 性能考虑:
- 相比于每次都使用
json.Marshal
对结构体的每个部分进行序列化,这种方式减少了json.Marshal
的调用次数,从而提升了性能。同时,一次遍历结构体字段也减少了反射的调用次数。
- 相比于每次都使用
注意,在实际生产中,如果性能要求极高,也可以考虑使用代码生成工具(如 gogoprotobuf
类似原理)来避免反射带来的性能损耗。