将接口{}反射为具体类型,然后执行类型断言。

huangapple go评论74阅读模式
英文:

Unmarshaling Into an Interface{} and Then Performing Type Assertion

问题

我通过rabbitmq消息系统获取一个string。在发送之前,我使用json.Marshal将结果转换为string,然后通过rabbitmq发送。

我要转换和发送的结构体可以是以下之一:(更改了结构体的名称和大小,但不应该影响)

type Something1 struct {
   Thing       string `json:"thing"`
   OtherThing  int64  `json:"other_thing"`
}

type Something2 struct {
   Croc   int   `json:"croc"`
   Odile  bool  `json:"odile"`
}

消息作为string完美地传递,并在另一端(某个服务器)打印出来。

到目前为止,一切都正常。现在我想将它们转换回它们的结构体并断言类型。

第一次尝试是:

func typeAssert(msg string) {
    var input interface{}
    json.Unmarshal([]byte(msg), &input)

    switch input.(type) {
    case Something1:
        job := Something1{}
        job = input.(Something1)
        queueResults(job)

    case Something2:
        stats := Something2{}
        stats = input.(Something2)
        queueStatsRes(stats)

    default:
    }
}

这不起作用。在解组后打印input的类型时,我得到了map[string]interface{}(?!)

更奇怪的是,映射键是我得到的字符串,而映射值为空。

我尝试了其他方法,例如:

func typeAssert(msg string) {
    var input interface{}
    json.Unmarshal([]byte(msg), &input)

    switch v := input.(type) {
    case Something1:
        v = input.(Something1)
        queueResults(v)

    case Something2:
        v = input.(Something2)
        queueStatsRes(v)

    default:
    }
}

我还尝试按照这个答案中所解释的方式编写switch语句:
Golang: cannot type switch on non-interface value

switch v := interface{}(input).(type) {

但仍然没有成功...

有什么想法吗?

英文:

I get a string through a rabbitmq message system. Before sending,

I use json.Marshal, convert the outcome to string and send through
rabbitmq.

The structs that I convert and send can be: (changed the names and the size of the structs but it should not matter)

type Somthing1 struct{
   Thing        string    `json:"thing"`
   OtherThing   int64     `json:"other_thing"`
}

or

type Somthing2 struct{
   Croc        int       `json:"croc"`
   Odile       bool      `json:"odile"`
}

The message goes through perfectly as a string and is printed
on the other side (some server)

Up until now everything works.
Now I'm trying to convert them back into their structs and assert the types.

first attempt is by:

func typeAssert(msg string) {

 var input interface{}

 json.Unmarshal([]byte(msg), &input)

 switch input.(type){
 case Somthing1:
    job := Somthing1{}
    job = input.(Somthing1)
    queueResults(job)

  case Somthing2:
    stats := Somthing2{}
    stats = input.(Somthing2)
    queueStatsRes(stats)
 default:
}

This does not work. When Printing the type of input after Unmarshaling
it I get map[string]interface{} (?!?)

and even stranger than that, the map key is the string I got and the map value is empty.

I did some other attempts like:

 func typeAssert(msg string) {

  var input interface{}

  json.Unmarshal([]byte(msg), &input)

  switch v := input.(type){
  case Somthing1:
    v = input.(Somthing1)
    queueResults(v)

   case Somthing2:
    v = input.(Somthing2)
    queueStatsRes(v)
  default:
}

and also tried writing the switch like was explained in this answer:
Golang: cannot type switch on non-interface value

switch v := interface{}(input).(type)

still with no success...

Any ideas?

答案1

得分: 45

json包在解组时默认的类型如下所示:

  • bool,用于JSON布尔值
  • float64,用于JSON数字
  • string,用于JSON字符串
  • []interface{},用于JSON数组
  • map[string]interface{},用于JSON对象
  • nil,用于JSON null

由于您正在解组到interface{}类型中,返回的类型将仅限于该集合。json包不知道Something1Something2。您需要将其从解组为map[string]interface{}的对象进行转换,或直接解组为所需的结构类型。

如果您不想从通用接口中解包数据,或者以某种方式标记数据以了解预期的类型,您可以迭代地将JSON数据尝试解组为每个所需的类型。

您甚至可以将它们打包到一个包装结构中,以便为您执行解组操作:

type Something1 struct {
    Thing      string `json:"thing"`
    OtherThing int64  `json:"other_thing"`
}

type Something2 struct {
    Croc  int  `json:"croc"`
    Odile bool `json:"odile"`
}

type Unpacker struct {
    Data interface{}
}

func (u *Unpacker) UnmarshalJSON(b []byte) error {
    smth1 := &Something1{}
    err := json.Unmarshal(b, smth1)

    // 没有错误,但我们还需要确保解组了某些内容
    if err == nil && smth1.Thing != "" {
        u.Data = smth1
        return nil
    }

    // 如果有错误,但错误类型不是类型错误,则中止
    if _, ok := err.(*json.UnmarshalTypeError); err != nil && !ok {
        return err
    }

    smth2 := &Something2{}
    err = json.Unmarshal(b, smth2)
    if err != nil {
        return err
    }

    u.Data = smth2
    return nil
}

您可以在此处查看示例代码:http://play.golang.org/p/Trwd6IShDW

英文:

The default types that the json package Unmarshals into are shown in the Unmarshal function documentation

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

Since you're unmarshaling into an interface{}, the returned types will only be from that set. The json package doesn't know about Something1 and Something2. You either need to convert from the map[string]interface{} that the json object is being unmarshaled into, or unmarshal directly into the struct type you want.

If you don't want to do unpack the data from a generic interface, or somehow tag the data so you know what type to expect, you could iteratively take the json and try to unmarshal it into each type you want.

You can even pack those into a wrapper struct to do the unmarshaling for you:

type Something1 struct {
	Thing      string `json:"thing"`
	OtherThing int64  `json:"other_thing"`
}

type Something2 struct {
	Croc  int  `json:"croc"`
	Odile bool `json:"odile"`
}

type Unpacker struct {
	Data       interface{}
}

func (u *Unpacker) UnmarshalJSON(b []byte) error {
	smth1 := &Something1{}
	err := json.Unmarshal(b, smth1)

	// no error, but we also need to make sure we unmarshaled something
	if err == nil && smth1.Thing != "" {
		u.Data = smth1
		return nil
	}

	// abort if we have an error other than the wrong type
	if _, ok := err.(*json.UnmarshalTypeError); err != nil && !ok {
		return err
	}

	smth2 := &Something2{}
	err = json.Unmarshal(b, smth2)
	if err != nil {
		return err
	}

	u.Data = smth2
	return nil
}

http://play.golang.org/p/Trwd6IShDW

答案2

得分: 27

你遇到了典型的 JSON 与强类型语言之间的问题!由于 JSON 是无类型且无模式的,无法在不解码的情况下推断出字符串下面的数据是什么。

因此,你唯一的选择是将其解组为 interface{},这将始终生成一个 map[string]interface{}。你可以在这里使用一些反射技巧来构建最终的结构体,但这需要大量的手动工作且容易出错。
以下是一些可能的解决方案:

快速而简单的方法

json 包处理反射工作。尝试将数据解组为每种预期的类型:

func typeAssert(msg string) {

 var thing1 Something1

 err := json.Unmarshal([]byte(msg), &thing1)
 if err == nil{
    // 处理 thing1
    return
 }    

 var thing2 Something2

 err = json.Unmarshal([]byte(msg), &thing2)
 if err == nil{
    // 处理 thing2
    return
 }    

 // 处理不支持的类型

}

在 JSON 上构建自己的“类型系统”

推迟编码,直到你知道其中的内容。使用以下结构体作为数据的中间表示:

type TypedJson struct{
  Type string 
  Data json.RawMessage
}

编码:

thing := Something1{"asd", 123}
tempJson, _ := json.Marshal(thing)

typedThing := TypedJson{"something1", tempJson}
finalJson, _ := json.Marshal(typedThing)

解码:

func typeAssert(msg string) {

  var input TypedJson  
  json.Unmarshal([]byte(msg), &input)

  switch input.Type{
  case "something1":
    var thing Something1
    json.Unmarshal(input.Data, &thing)
    queueStatsRes(thing)   
   case "something2":
    var thing Something2
    json.Unmarshal(input.Data, &thing)
    queueStatsRes(thing)
  default:
    // 处理不支持的类型
}

使用强类型的序列化格式

英文:

You have encountered a typical json vs typed language problem!
Since json is untyped and schemaless, it is not possible to infer what data is "under the string" without actually decoding it.

So your only option is to unmarshal into an interface{} which always produces a map[string]interface{}. You could do some reflection magic here to build the final struct, but that's a lot of manual work and error prone.
Here are some possible solutions:

Quick 'n' dirty

Let the json package do the reflection stuff. Attempt to unmarshal into every expected type:

func typeAssert(msg string) {

 var thing1 Something1

 err := json.Unmarshal([]byte(msg), &thing1)
 if err == nil{
    // do something with thing1
    return
 }    

 var thing2 Something2

 err = json.Unmarshal([]byte(msg), &thing2)
 if err == nil{
    // do something with thing2
    return
 }    
 
 //handle unsupported type

}

Build your own "type system" on top of json

Defer the encoding until you know what's inside. Use this struct as an intermediate representation of your data:

type TypedJson struct{
  Type string 
  Data json.RawMessage
}

Marshal:

thing := Something1{"asd",123}
tempJson, _ := json.Marshal(thing)
 
typedThing := TypedJson{"something1", tempJson}
finalJson, _ := json.Marshal(typedThing)

Unmarshal:

func typeAssert(msg string) {

  var input TypedJson  
  json.Unmarshal([]byte(msg), &input)

  switch input.Type{
  case "something1":
    var thing Something1
    json.Unmarshal(input.Data, &thing)
    queueStatsRes(thing)   
   case "something2":
    var thing Something2
    json.Unmarshal(input.Data, &thing)
    queueStatsRes(thing)
  default:
    //handle unsupported type
}

Use a typed serialization format

答案3

得分: 0

我喜欢这种“Message”类型的风格,它可以解析任何预期的消息。

一些好处:

  • 它在更大的JSON结构中作为子类型时表现良好,因为它实现了UnmarshalJSON。这使得它更具可重用性。
  • 消息始终解析为相同的类型,因此我们是静态类型的,但类型字段告诉您它是什么类型的消息。
  • Something1/Something2字段是强类型的,但只有正确的字段被填充。

我希望顶层消息有一个明确的类型。类似于{"messageType":"Something1", "messageData":{...}}这样的形式。这将消除解析过程中的试错因素。但是您并不总是能够控制数据源,可能需要采用其他方法。

type Something1 struct{
   Thing        string    `json:"thing"`
   OtherThing   int64     `json:"other_thing"`
}

type Something2 struct{
   Croc        int       `json:"croc"`
   Odile       bool      `json:"odile"`
}

type Message struct{
  Type string // 这里可以使用枚举类型,但为了简洁起见,使用字符串

  // 使用指针,因为只有其中一个会被填充,另一个将为nil。可以根据需要添加任意数量的消息类型。
  Something1 *Something1
  Something2 *Something2
}

func (m *Message) UnmarshalJSON(b []byte) error {
  var s1 Something1
  err := json.Unmarshal(b, &s1)
  if err == nil {
    // 这一行是某种检查,用于验证s1是否为有效的Something1。具体取决于用例/数据模型。
    if s1.Thing != "" {
      m.Type = "Something1"
      m.Something1 = &s1
      return nil
    }
  }

  var s2 Something2
  err = json.Unmarshal(b, &s2)
  if err == nil {
    // 这一行是某种检查,用于验证s2是否为有效的Something2。具体取决于用例/数据模型。
    if s2.Croc > 0 {
      m.Type = "Something2"
      m.Something2 = &s2
      return nil
    }
  }

  return errors.New("无效的消息")
}

示例(基于JimB的示例):https://go.dev/play/p/vQfY--lSGmh

英文:

I like this style of a "Message" type which can unmarshal any expected message.

Few benefits:

  • It plays nice with being a subtype in a bigger json structure because it implements UnmarshalJSON. This makes it more reusable.
  • Message is always parsed to the same type so we're statically typed, but a type field tells you what type of message it is
  • The Somthing1/Somthing2 fields are strongly typed, but only the correct one is populated.

I would prefer if the top level message had an explicit type. Something like {"messageType":"Somthing1", "messageData":{...}}. That would take the trial and error aspect out of parsing. But you can't always control the data source and may need to resort to it.

type Somthing1 struct{
   Thing        string    `json:"thing"`
   OtherThing   int64     `json:"other_thing"`
}

type Somthing2 struct{
   Croc        int       `json:"croc"`
   Odile       bool      `json:"odile"`
}

type Message struct{
  Type string // enum type here would be nice, but string for brevity

  // pointers, because only one of these will be populated, and the other will be nil. Can add as many as you want as you add message types.
  Somthing1 *Somthing1
  Somthing2 *Somthing2
}

func (m *Message) UnmarshalJSON(b []byte) error {
  var s1 Somthing1
  err := json.Unmarshal(b, &s1)
  if err == nil {
    // this line is some sort of check s1 is a valid Somthing1. Will depend on use case/data model
    if s1.Thing != "" {
      m.Type = "Somthing1"
      m.Somthing1 = &s1
      return nil
    }
  }

  var s2 Somthing2
  err = json.Unmarshal(b, &s2)
  if err == nil {
    // this line is some sort of check s2 is a valid Somthing2. Will depend on use case/data model
    if s2.Croc > 0 {
      m.Type = "Somthing2"
      m.Somthing2 = &s2
      return nil
    }
  }

  return errors.New("Invalid message")
}

Example (based off of JimB's sample): https://go.dev/play/p/vQfY--lSGmh

huangapple
  • 本文由 发表于 2016年2月24日 01:05:42
  • 转载请务必保留本文链接:https://go.coder-hub.com/35583735.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定