【问题标题】:why unmarshal make the type of object changed in golang为什么 unmarshal 在 golang 中改变对象的类型
【发布时间】:2018-11-23 03:19:15
【问题描述】:

我想写一个 mockData 方法,它可以接受多种类型的参数并根据其 json 数据返回相应的对象。代码如下:

func MockData(jsonPath string,v interface{})(interface{},error){
    var ret interface{}
    data,_ := ioutil.ReadFile(jsonPath)    
    switch v.(type) {
    case Req:
        ret = Req{}
        fmt.Printf("\n===before Unmarshal==%T===\n",ret)
        err = json.Unmarshal(data,&ret) 
        if err!=nil{...}
        fmt.Printf("======after unmarshal===%T\n",ret)
    case ...
    default:
        fmt.Printf("error===not match")
   }
   return ret,err
}

但是,当我使用它时,它会出现恐慌。代码如下:

func main(){
    reqJsonPath := /xx/yy/req.json
    obj,err:=test.MockData(jsonFile,Req{})
    if err!=nil{...}
    require := obj.(Req) //panic cant []interface{} to Req
}

MockData 的输出是:

===before Unmarshal==Req===
======after unmarshal===[]interface{}

对象的类型解组后更改。更奇怪的是,如果我更换:

ret = Req{}

ret = &Req{}

输出将如下所示:

===before Unmarshal==*Req===
======after unmarshal===*Req

为了更方便地重现问题,我给出了 Require 结构如下:

type Req []*Ele
type Ele struct {
   ID   int 
   Level int   
}

总结

  1. 能否实现预期的功能,根据其 json 和类型生成不同类型的对象?
  2. 为什么解组后对象的类型改变了,为什么我添加 & 后它没有改变?

【问题讨论】:

  • 变量retinterface{} 类型,该类型用于解组。反射很复杂,而且您的 Printfs 不能像您期望的那样工作。阅读博客文章反射定律。您实际上必须解组为 common.Require(而不是空接口),因此将变量 decl 移动到类型开关中。
  • common.Require 定义在哪里?
  • @Flimzy 哦,为了简化问题,我将 common.Require 替换为 Req,我会更新它。

标签: go interface unmarshalling


【解决方案1】:

我能否实现预期的功能,根据其 json 和类型生成不同类型的对象?

func MockData(filename string, v interface{}) (interface{}, error) {

    data, _ := ioutil.ReadFile(filename)
    switch t := v.(type) {
    case Req:
        // t at this point is a Req{}
        err := json.Unmarshal(data, &t)
        return t, err
    }
    return nil, errors.New("unknown type")
}

我真的不知道您为什么需要传递实际结构而不是指针的动机。 Check this demonstration

为什么unmarshal后对象的类型变了,为什么我加&后没有变?

当您使用&ret 解组时,其中ret 是一个接口,您将获得该接口的地址。因此,json.Unmarshal() 将看到支持数据是一个接口,而不是一个指向结构的指针。 json.Unmarshal() 将使用的默认数据类型是 map[string]interface{} 用于对象,[]interface{} 用于数组。

现在,如果您使用ret 解组,其中ret&Req{}json.Unmarshal() 将检查支持数据是否为struct,因此它可以使用结构的字段进行解组。

编辑

您似乎对指向接口的指针 感到困惑,该指针不同于具有指针的接口。试试这段代码,你会看到不同。

var x interface{} = Req{}
var y interface{} = &x
var z interface{} = &Req{}

fmt.Printf("%T\n", y)
fmt.Printf("%T\n", z)

请记住,接口只是普通值,它们也占用内存。现在,如果您获取该内存的地址,您将获得指向接口的指针,而不是指向接口所指数据的指针。

【讨论】:

  • 如果我使用ret 解组,&ret 也是接口的地址。为什么json.Unmarshal() 会检查支持数据是struct
  • 因为它是指向结构的指针,而不是指向接口的指针。
【解决方案2】:
  1. 能否实现预期的功能,根据其 json 和类型生成不同类型的对象?

是的,但是您必须在调用端使用类型断言将其转换回来,即

  MyFoo:=MockData("foo.json", Foo{}).(Foo)

(或函数中有多个return ret.(Foo)return ret.(Bar)

  1. 为什么解组后对象的类型改变了,为什么我添加 & 后它没有改变?

Unmarshal 源代码顶部有一些有用的 cmets 即

// To unmarshal JSON into a pointer, Unmarshal first handles the case of
// the JSON being the JSON literal null. In that case, Unmarshal sets
// the pointer to nil. Otherwise, Unmarshal unmarshals the JSON into
// the value pointed at by the pointer. If the pointer is nil, Unmarshal
// allocates a new value for it to point to.

// To unmarshal JSON into an interface value,
// Unmarshal stores one of these in the interface value:
//
//  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

所以在第一种情况下,您将解组为接口值(ret 被声明为接口{}) 在第二种情况下,有一个指向结构的指针,这就是你得到的

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-09-29
    • 1970-01-01
    • 2013-07-15
    • 2014-03-22
    • 1970-01-01
    • 2018-07-29
    • 1970-01-01
    相关资源
    最近更新 更多