如何在golang中解析JSON而不解组两次

时间:2017-10-03 02:12:57

标签: json go

我有一个Web套接字连接,它在JSON对象中发送不同类型的消息,我想将内容解组为一些已知的结构。 要做到这一点,我想我应该做到以下几点:

步骤1)将JSON解组为通用映射[string] interface {}

步骤2)找到我正在寻找的钥匙

步骤3)尝试将值转换为我的一个类型(这失败)

步骤3交替)json编组该值并将其解组为我已知的struct

如果我尝试使用myStruct, ok := value.(myType)它会失败,但是如果我json.marshal(value)然后json.unmarshal到myStruct,它就可以正常工作。那是我应该这样做的吗?去json-> map [string] interface {} - > json - > myStruct对我来说似乎是多余的。

示例代码:

https://play.golang.org/p/to_0Id_ja9

package main

import (
    "encoding/json"
    "fmt"
)

type Ping struct {
    Ping string `json:"ping"`
}

type Ack struct {
    Messages []Message `json:"messages"`
}

type Message string

func main() {
    testJSON := []byte(`{"ack":{"messages":["Hi there","Hi again"]}}`)
    var myAck = Ack{}
    var myMap map[string]interface{}
    err := json.Unmarshal(testJSON, &myMap)
    if err != nil {
        fmt.Println("error unmarshalling: ", err)
    }
    for k, v := range myMap {
        fmt.Printf("key: %s, value: %s \n", k, v)

        switch k {
        case "ping":
            fmt.Println(k, " is a ping", v)
        case "ack":
            fmt.Println(k, " is an ack containing a message list")
            ackjson, err := json.Marshal(v)
            if err != nil {
                fmt.Println("marshal error: ", err)
            }
            err = json.Unmarshal(ackjson, &myAck)
            if err != nil {
                fmt.Println("unmarshal error", err)
            } else {
                fmt.Println("New ack object: ", myAck)
            }
        default:
            fmt.Printf("%s is of a type (%T) I don't know how to handle", k, v)
        }
    }
}

3 个答案:

答案 0 :(得分:2)

一种解决方案是通过将值解组为json.RawMessage而不是interface{}来部分解组数据

var myMap map[string]json.RawMessage

稍后在开关中,仍然需要,你不需要编组。只是做:

err = json.Unmarshal(v, &myAck)

游乐场: https://play.golang.org/p/NHd3uH5e7z

答案 1 :(得分:-1)

您需要创建一个结构,其中包含您可能接收的所有键,然后解组一次,最后根据.dumps哪些键可以决定您获得的数据包类型。

以下示例显示了此行为:https://play.golang.org/p/aFG6M0SPJs

nil

答案 2 :(得分:-1)

另一个解决方案是在所有消息之间使用公共密钥(例如:type),它指示消息的类型。在此之后,如果消息不够复杂,您可以从其他映射键/类型断言映射值构建原始结构:

我写的一些相关的websocket代码就是这样做的: https://github.com/blaskovicz/cut-me-some-slack/blob/master/chat/message.go#L66

func DecodeClientMessage(c *ClientMessage) (typedMessage interface{}, err error) {
    buff := map[string]string{}
    err = json.NewDecoder(bytes.NewReader(c.Raw)).Decode(&buff)
    if err != nil {
        return
    }

    switch t := buff["type"]; t {
    case "message":
        channelID := buff["channel_id"]
        if channelID == "" {
            err = fmt.Errorf("invalid client message received: missing channel_id")
            return
        }
        cms := &ClientMessageSend{ChannelID: channelID, Text: buff["text"]}
        if cms.Text == "" {
            err = fmt.Errorf("invalid client message received: missing text")
        } else {
            typedMessage = cms
        }

...然后调用者代码执行result.(type)断言并切换: https://github.com/blaskovicz/cut-me-some-slack/blob/master/chat/hub.go#L150

switch m := raw.(type) {
case *ClientMessageHistory:
    channelID := h.resolveSlackChannel(m.ChannelID)
    if channelID == "" {
        log.Printf("error: no channel found matching %s\n", m.ChannelID)
        return
    }
    var username string
    if c.Client.User != nil {
        username = c.Client.User.Username
    } else {
        username = "<anonymous>"
    }
    log.Printf("sending previous messages for channel %s to client %s\n", channelID, username)
    for _, prevMessage := range h.previousMessages(channelID, m.Limit) {
        c.Client.send <- prevMessage
    }