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
这篇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)
这是成功的标志!
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))
}
检查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库进行解码。方案二和方案四提供了更好的类型安全性和扩展性。


