解封到接口{},然后执行类型断言



我通过rabbitmq消息系统获得string。在发送之前,

我使用json.Marshal,将结果转换为string并通过发送兔子。

我转换和发送的结构可以是:(更改了结构的名称和大小,但这无关紧要)

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

type Somthing2 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 Somthing1:
    job := Somthing1{}
    job = input.(Somthing1)
    queueResults(job)
  case Somthing2:
    stats := Somthing2{}
    stats = input.(Somthing2)
    queueStatsRes(stats)
 default:
}

这行不通。在取消编组后打印input类型时我得到map[string]interface{} (?!?)

更奇怪的是,map

键是我得到的字符串,map值为空。

我做了一些其他尝试,例如:

 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:
}

并且还尝试编写开关,就像这个答案中解释的那样:Golang:无法在非接口值上键入开关

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

仍然没有成功...

有什么想法吗?

json 将解编组打包到的默认类型显示在Unmarshal函数文档中

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

由于您正在解组到interface{},因此返回的类型将仅来自该集合。json包不知道Something1Something2.您需要从 json 对象被取消封送到的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)
    // 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

您遇到了典型的 json 与类型语言问题!由于 json 是非类型化和无模式的,因此如果不实际解码它,就不可能推断出哪些数据"在字符串下"。

因此,您唯一的选择是解组到始终产生map[string]interface{}interface{}中。你可以在这里做一些反射魔法来构建最终的结构,但这需要大量的手动工作和容易出错。以下是一些可能的解决方案:

快速'n'脏

json包做反射的事情。尝试取消编组到每个预期的类型:

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
}

在 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:
    //handle unsupported type
}

使用类型化序列化格式

  • Go 自己的 gob 编码
  • 协议缓冲区
  • 还有更多...

我喜欢这种"消息"类型的风格,它可以取消任何预期的消息。

几个好处:

  • 它非常适合作为更大的 json 结构中的子类型,因为它实现了UnmarshalJSON .这使得它更可重用。
  • 消息
  • 始终解析为相同的类型,因此我们是静态类型的,但类型字段会告诉您它是什么类型的消息
  • Somthing1/Somthing2 字段是强类型字段,但只填充正确的字段。

我希望顶级消息具有显式类型。像{"messageType":"Somthing1", "messageData":{...}}.这将消除解析中的试错方面。但是,您不能始终控制数据源,可能需要求助于它。

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")
}

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

最新更新