Golang如何实现未知结构类型的JSON解码

Golang如何实现未知结构类型的JSON解码 我正在编写一个遵循协议规范的Minecraft库。目前正在尝试接收状态数据包。JSON编解码器会解码并将未序列化的JSON结构体以映射形式返回。然后我使用mapstructure包来解码这个映射。但这需要预先知道数据包的最终类型,才能将结构体作为输出目标。我不知道如何确定与解码返回的映射相匹配的结构体。

是否有更好的方法将JSON反序列化以返回正确的结构体?

以下是返回映射形式结构体的JSON解码方法:

func ReadJSON(reader io.Reader) (val map[string]interface{}, err error) {
    	var v map[string]interface{}

    	bytes, err := ioutil.ReadAll(reader)
    	if err != nil {
    		return nil, err
    	}
    	//将从字节数组中第一个左花括号开始进行反序列化
    	err = json.Unmarshal(bytes[strings.IndexRune(string(bytes), '{'):], &v)
    	return v, err
}

以下是解码方法: 原始代码由justblender编写,我正在尝试添加结构体支持。

func (c *Connection) decode(p *Packet) (packets.Holder, error) {
	holder, ok := packetList[p.Direction][c.State][p.ID]
	if !ok {
		return nil, UnknownPacketType
	}
	inst := reflect.New(holder).Elem()

	for i := 0; i < inst.NumField(); i++ {
		field := inst.Field(i)

		codec, ok := field.Interface().(codecs.Codec)
		if !ok {
			if field.Kind() == reflect.Struct {
				codec = codecs.JSON{V: field.Interface()}
			} else {
				return nil, codecs.UnknownCodecType
			}
		}

		value, err := codec.Decode(&p.Data)
		if err != nil {
			return nil, fmt.Errorf("packet decode failed: %s", err)
		}

		if reflect.TypeOf(codec) == reflect.TypeOf(codecs.JSON{}) {
			switch values_type {//这是确定结构体的部分
			case equals packets.StatusResponse{}:
				pkt := packets.StatusResponse{}
				err = mapstructure.Decode(value, &pkt.status)
				if err != nil {
					return nil, fmt.Errorf("mapstructure decode failed: %s", err)
				}
                value = pkt
			}
		}
		field.Set(reflect.ValueOf(value))
	}

	return inst.Interface().(packets.Holder), nil
}

如果需要更多代码,请查看justblender的GitHub,因为改动非常少。

最终目标是接收JSON并返回结构体:

type StatusResponse struct {
	Status struct {
		Version struct {
			Name     string `json:"name"`
			Protocol int    `json:"protocol"`
		} `json:"version"`

		Players struct {
			Max    int `json:"max"`
			Online int `json:"online"`
		} `json:"players"`

		Description chat.TextComponent `json:"description"`
	}
}

需要说明的是,我对JSON和反射了解不多,如果我的实现存在任何问题,请礼貌指正。如果有更简单的方法,请告诉我。我现在非常困惑,只是想学习相关知识。


更多关于Golang如何实现未知结构类型的JSON解码的实战教程也可以访问 https://www.itying.com/category-94-b0.html

6 回复

这篇Go语言博客文章看起来正好回答了你的问题: https://blog.golang.org/json-and-go

这是一篇非常有指导意义的文章,包含了结构清晰的示例。

更多关于Golang如何实现未知结构类型的JSON解码的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


可以使用 decoder.Decode 方法:

// import "encoding/json"


statusResponse := new(StatusResponse)
decoder := json.NewDecoder(r.Body) // 这里放入用于Unmarshal的字节数据
err := decoder.Decode(&statusResponse)
if err != nil {
      panic(err)
}

fmt.Println(statusResponse)

也许确实如此。但问题在于我不知道传输过来的是 StatusResponse 还是其他数据包。我卡在如何判断传入数据包类型这个问题上了。

我最初实现功能的方式是检查连接状态。如果状态等于 status,我就知道这是 StatusResponse,因为此时只可能是这种类型。但在其他状态下,可能存在多种可能的数据包类型。

这是成功的标志!

Version: 1.13.2
Protocol: 404
Description: A Minecraft Server
Players: 0/20

我一直在努力弄清楚这个数据包是什么。但每个数据包都会发送其ID(当然啦😂),所以我只需在解码函数中切换ID并返回正确的结构体!太棒了!

感谢提供有用的JSON网站。这真的让我弄明白了问题。

或许,你可以尝试对要查找的结构体进行类型断言

package main

import "fmt"

type Test struct {
    foo int
}

func isTest(t interface{}) bool {
    switch t.(type) {
    case Test:
        return true
    default:
        return false
    }
}

func main() {
    t := Test{5}
    fmt.Println(isTest(t))
}

ollien

检查Go中结构体的类型

标签: go, reflection

在Go语言中处理未知结构的JSON解码,可以通过几种方式实现。以下是针对你的场景的解决方案:

方案一:使用 json.RawMessage 进行延迟解码

type PacketWrapper struct {
    Type    string          `json:"type"`
    Payload json.RawMessage `json:"payload"`
}

func decodePacket(data []byte) (packets.Holder, error) {
    var wrapper PacketWrapper
    if err := json.Unmarshal(data, &wrapper); err != nil {
        return nil, err
    }
    
    // 根据类型字段决定具体结构体
    switch wrapper.Type {
    case "status_response":
        var statusResp packets.StatusResponse
        if err := json.Unmarshal(wrapper.Payload, &statusResp); err != nil {
            return nil, err
        }
        return &statusResp, nil
    case "ping":
        var ping packets.Ping
        if err := json.Unmarshal(wrapper.Payload, &ping); err != nil {
            return nil, err
        }
        return &ping, nil
    default:
        return nil, fmt.Errorf("unknown packet type: %s", wrapper.Type)
    }
}

方案二:使用接口和自定义UnmarshalJSON

type Packet interface {
    GetType() string
}

type StatusResponse struct {
    Status struct {
        Version struct {
            Name     string `json:"name"`
            Protocol int    `json:"protocol"`
        } `json:"version"`
        Players struct {
            Max    int `json:"max"`
            Online int `json:"online"`
        } `json:"players"`
        Description chat.TextComponent `json:"description"`
    } `json:"status"`
}

func (s *StatusResponse) GetType() string {
    return "status_response"
}

type GenericPacket struct {
    Type string `json:"type"`
    Data json.RawMessage `json:"data"`
}

func DecodePacket(data []byte) (Packet, error) {
    var generic GenericPacket
    if err := json.Unmarshal(data, &generic); err != nil {
        return nil, err
    }
    
    switch generic.Type {
    case "status_response":
        var resp StatusResponse
        if err := json.Unmarshal(generic.Data, &resp); err != nil {
            return nil, err
        }
        return &resp, nil
    default:
        return nil, fmt.Errorf("unknown packet type: %s", generic.Type)
    }
}

方案三:改进你的现有代码

func (c *Connection) decode(p *Packet) (packets.Holder, error) {
    holder, ok := packetList[p.Direction][c.State][p.ID]
    if !ok {
        return nil, UnknownPacketType
    }
    
    // 直接使用JSON解码到目标结构体
    target := reflect.New(holder).Interface()
    
    // 查找JSON数据的位置
    jsonStart := strings.IndexRune(string(p.Data), '{')
    if jsonStart == -1 {
        return nil, fmt.Errorf("no JSON data found in packet")
    }
    
    // 直接解码到目标结构体
    if err := json.Unmarshal(p.Data[jsonStart:], target); err != nil {
        return nil, fmt.Errorf("JSON decode failed: %s", err)
    }
    
    return target.(packets.Holder), nil
}

方案四:使用类型注册表

var packetRegistry = map[string]func() packets.Holder{
    "status_response": func() packets.Holder { return &packets.StatusResponse{} },
    "ping":           func() packets.Holder { return &packets.Ping{} },
}

func DecodeDynamicPacket(data []byte) (packets.Holder, error) {
    // 先解析类型字段
    var typeInfo struct {
        PacketType string `json:"packet_type"`
    }
    if err := json.Unmarshal(data, &typeInfo); err != nil {
        return nil, err
    }
    
    // 从注册表获取构造函数
    constructor, exists := packetRegistry[typeInfo.PacketType]
    if !exists {
        return nil, fmt.Errorf("unknown packet type: %s", typeInfo.PacketType)
    }
    
    // 创建实例并解码
    instance := constructor()
    if err := json.Unmarshal(data, instance); err != nil {
        return nil, err
    }
    
    return instance, nil
}

简化版的ReadJSON函数

func ReadJSONToStruct(reader io.Reader, target interface{}) error {
    bytes, err := ioutil.ReadAll(reader)
    if err != nil {
        return err
    }
    
    jsonStart := strings.IndexRune(string(bytes), '{')
    if jsonStart == -1 {
        return fmt.Errorf("no JSON data found")
    }
    
    return json.Unmarshal(bytes[jsonStart:], target)
}

// 使用示例
func handleStatusResponse(reader io.Reader) (*packets.StatusResponse, error) {
    var resp packets.StatusResponse
    if err := ReadJSONToStruct(reader, &resp); err != nil {
        return nil, err
    }
    return &resp, nil
}

这些方法避免了使用mapstructure的中间映射步骤,直接利用Go的标准JSON库进行解码。方案二和方案四提供了更好的类型安全性和扩展性。

回到顶部