Decode JSON to struct without knowing struct type

I am writing a Minecraft library which follows the protocol specs here. I am attempting to receive the status packet. The json codec decodes and returns the unmarshalled json struct in a map. Then, I decode the map using the mapstructure package. But this requires knowing the final type of the packet so you can send it the struct for output. I don’t know how to determine the struct that matches the map returned from the decode.

Is there a better way to unmarshal the json to return the correct struct?

Here is the JSON decode method that returns the struct in a map:

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
    	}
    	//Will unmarshal starting at the first open brace in the byte[]
    	err = json.Unmarshal(bytes[strings.IndexRune(string(bytes), '{'):], &v)
    	return v, err
}

Here is the decode method:
The original code was written by justblender I’m attempting to add struct support.

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 {//This is the part where the struct is determined
			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
}

If you need more code look at justblender’s github because very little has changed.

The final goal is to take the JSON that is recieved and return the struct:

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

Know that I don’t know a lot about JSON, or reflection so if I have done some stupid stuff correct politely. If there is an easier way please let me know. I am very lost and I’m just trying to learn.

You can use the decoder.Decode;

// import "encoding/json"


statusResponse := new(StatusResponse)
decoder := json.NewDecoder(r.Body) // here you put your byte data for Unmarshal
err := decoder.Decode(&statusResponse)
if err != nil {
      panic(err)
}

fmt.Println(statusResponse)

Perhaps, yes. But the problem is I don’t know if it’s a StatusResponse coming through or some other packet. I’m stumped on how to figure out the type of packet coming in.

The way I originally got it to work was to check the state of the connection. If the state == status then I knew it was a StatusResponse because that’s all it could be. But in other states there are multiple things it can be.

Maybe, you can try make the type assertions of the struct you’re are looking for

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

This golang blog article looks like it addresses your question exactly:
https://blog.golang.org/json-and-go

It’s a really instructional article with well formed examples.

This is the sign of success!

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

I was struggling to find out what the packet was. But each packet sends it’s ID (lol of course :man_facepalming:) so I just switched the id in a decode function and returned the correct struct! Woohoo!

Thank you for the useful JSON website. It really cleared things up for me.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.