Golang中json字符串反序列化时的一个问题剖析

1,701 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

1、前言

一次在json字符串反序列化为interface{}时遇到的一个问题。 首先先看这样一段代码:

import (
   "encoding/json"
   "fmt"
   "testing"
)

type Demo struct {
   Errno int32 `json:"errno"`
   ErrMsg string `json:"errmsg"`
   Data []string `json:"data"`
}

type Result struct {
   Errno int32 `json:"errno"`
   ErrMsg string `json:"errmsg"`
   Data interface{} `json:"data"`
}
 
func TestA(t *testing.T)  {
   demo := Demo{
      Errno: 0,
      ErrMsg: "success",
      Data: []string{"hello", "world"},
   }
   // 序列化Demo结构体
   s,_ := json.Marshal(demo)
   var res Result
   // 反序列化
   _ = json.Unmarshal(s, &res)
   data, ok := res.Data.([]string)
   fmt.Println("data:", data)   // []
   fmt.Println("ok:", ok)       // false
}

反序列化时,用interface{}类型接收Data字段数据。在转换为原来的[]string类型时,发现转换失败了。 打印反序列化后的Data字段类型是:[]interface{}

fmt.Printf("%T\n", res.Data) // []interface {}

image.png

2、分析

由打印结果引出两问题:

  1. 为什么[]interface{}类型转换为[]string类型时会失败?
  2. 为什么原来[]string类型的字段,json先序列化再反序列化,类型就会变成[]interface{}?

我们在IDE做个小测试,代码如下👇:

var dataSlice = []string{
   "a",
   "b",
}

var interfaceSlice []interface{} = dataSlice

结果这段代码会在IDE中会直接报错: Cannot use 'dataSlice' (type []string) as the type []interface{}
这个与我们预期想法不一致,理论上可以把任何类型赋值给interface{},但是上面👆case却发现不能把任何类型的切片赋值到[]interface{}

3、原因

3.1、类型转化失败

  1. []interface{}类型不是interface{}类型,它是一个切片,切片元素的类型恰好是interface{}
  2. []interface{}类型变量拥有特定的内存结构,这在编译时就已经决定了。

每个interface{}占两个字(word),一个字用于存放interface存放的类型,另一个字用于存放实际数据或者是指向数据的指针。于是长度为N的[]interface{}类型切片背后是一个N*2字长的一块数据。

这与一般的[]MyType类型切片不同,相同长度的[]MyType切片背后的数据块大小为N*sizeof(MyType)字长。

正是这两个原因决定了不能直接将某些[]MyType切片赋值给[]interface{}, 他们背后代表的数据意义不同。

同理我们也无法将[]interface{}类型转换为[]string类型。

3.2、序列化后反序列化失败

这个问题存在的前提是,我们用一个interface{}类型来接收json反序列化后的字段。如果要将一个json字符串反序列化到一个interface字段上,则会返回以下集合对应的类型:

bool, for JSON booleans
float64, for JSON numbers
string, for JSON strings
[]interface{}, for JSON arrays
map[string]interface{}, for JSON objects
nil for JSON null

也就是对于所有的json array类型,都将得到[]interface{}类型。

4、解决方案

通过以上对问题的定位和分析,当我们需要得到反序列化后slice里面的string类型时,只能通过遍历[]interface{}类型切片,循环地将每一个元素转换为string类型了,否则就需要明确地使用[]string来接收反序列化的字段了。
for example:

var res Result
_ = json.Unmarshal(s, &res)
 
data1, ok := res.Data.([]interface{})
for _, item := range data1 {
   s, ok := item.(string)
   fmt.Println(s, ok) 
}
// log
//hello true
//world true

参考链接🔗:
1、github.com/golang/go/w…
2、pkg.go.dev/encoding/js…