Unmarshalling Dynamic JSON Data With Overlapping Fields in Golang

Issue

Sorry If i’m posting a question that has already been answered, but I can’t seem to find any similar situations on here. I have a websocket client that receives dynamic json data with overlapping fields. The fact that the fields overlap has has made Unmarshalling very difficult for me.

I have structs for the data types I receive, but I need a way to check the json data before I unmarshal it to a specific struct. I was hoping that an interface could act as a temporary holder and I would then be able to match the interface to the specific struct I want to unmarshal to, but that doesn’t seem possible, or I just don’t know how to go about it. Here are a few examples of the data types I’m receiving and structs to go along with it in case that helps.

response 1: {"connectionID":17973829270596587247,"event":"systemStatus","status":"online","version":"1.9.0"}
response 2: {"channelID":328,"channelName":"ohlc-5","event":"subscriptionStatus","pair":"XBT/USD","status":"subscribed","subscription":{"interval":5,"name":"ohlc"}}
response 3: [328,["1649576721.042916","1649577000.000000","42641.50000","42641.50000","42641.50000","42641.50000","42641.50000","0.00335101",2],"ohlc-5","XBT/USD"]
response 4: {"event":"heartbeat"}

structs below
import (
    "time"
    "encoding/json"
)

type ConnStatus struct {
    ConnectionID        uint64          `json:"connectionID"`
    Event               string          `json:"event"`
    Status              string          `json:"status"`
    Version             string          `json:"version"`
}

type HeartBeat struct {
    Event               string          `json:"event"`
}

type OHLCsuccess struct {
    ChannelID           int             `json:"channelID"`
    ChannelName         string          `json:"channelName"`
    Event               string          `json:"event"`
    Pair                string          `json:"pair"`
    Status              string          `json:"status"`
    Subscription        OHLC            `json:"subscription"`
}

type OHLC struct {
    Interval        int         `json:"interval"`
    Name            string      `json:"name"`
}

type OHLCUpdates struct {
    ChannelID           int
    OHLCArray           OHLCNewTrade
    ChannelName         string
    Pair                string
}

type OHLCNewTrade struct {
    StartTime           UnixTime
    EndTime             UnixTime
    Open                float64
    High                float64
    Low                 float64
    Close               float64
    VWAP                float64
    Volume              float64
    Count               int
}

type UnixTime struct {
    time.Time
}

func (u *UnixTime) UnmarshalJSON(d []byte) error {
    var ts int64
    err := json.Unmarshal(d, &ts)
    if err != nil {
        return err
    }
    u.Time = time.Unix(ts, 0).UTC()
    return nil
}

Any idea(s) on how to go about this? Thanks in advance for the help!

Solution

Are you in control of the different responses? If so, wow about adding a "type" field to the top level?

See "How to put everything at the top level" section on https://eagain.net/articles/go-dynamic-json/ for more info.

E.g. (untested):

func UnmarshalJSON(d []byte) error {
    var jsonValue map[string]interface{}
    err := json.Unmarshal(d, &jsonValue)

    if err != nil {
        return err
    }

    switch jsonValue["type"] {
    case 1:
        // unmarshal into struct type 1
    case 2:
        // unmarshal into struct type 2
    default:
        // throw err
    }

    // or if you don't have access to type:
    if jsonValue["connectionID"] != nil {
        // unmarshal into struct type 1
    }

    return nil
}

Alternatively you could try to (strictly) unmarshal into each struct, until you don’t get an error, e.g. something like:

func DetermineStruct(d []byte) int {
    var connStatus *ConnStatus

    reader := bytes.NewReader(d)
    decoder := json.NewDecoder(reader)
    decoder.DisallowUnknownFields()

    err := decoder.Decode(connStatus)
    if err == nil {
        panic(err)
    }

    err = json.Unmarshal(d, &connStatus)
    if err == nil {
        return 1
    }

    var ohlcSuccess OHLCsuccess
    err = json.Unmarshal(d, &ohlcSuccess)
    if err == nil {
        return 2
    }
}

Answered By – ECH

Answer Checked By – Mildred Charles (GoLangFix Admin)

Leave a Reply

Your email address will not be published.