简体   繁体   中英

Most efficient way to dynamically unmarshall JSON in Golang

Using Golang, I need to unmarshall nested JSON messages with a finite number of structures and a common head. The head contains type information which identifies what detail structures the JSON message contains. What I try to do is to extract the type information from the head and then dynamically select the structure for the details. Stylized example JSON strings look like this:

{"type":"a_number", "data":{"somenumber":1234}}
{"type":"b_string", "data":{"somestring":"a string", "anotherstring": "a second string"}}`

My initial approach was to use structures as follows:

type Head struct {
    Type  string `json:"type"`
    Data interface{} `json:"data"`
}

type A struct {
    SomeNumber decimal.Decimal `json:"somenumber"`
}

type B struct {
    SomeString string `json:"somestring"`
    AnotherString string `json:"anotherstring"`
}

I tried the to use interface.(type) on the data element to check which structure would be applicable, like so:

var msg Head

json.Unmarshal([]byte(jsonString), &msg)

switch v := msg.Data.(type) {
    case A:
        fmt.Printf("Type is A (%v)", v)
        detail := msg.Data.(A)
        fmt.Println(detail.SomeNumber)
    case B:
        fmt.Printf("Type is B (%v)", v)
        detail := msg.Data.(B)
        fmt.Println(detail.SomeString)
    default:
        fmt.Printf("I don't know about type %T!\n", v)
}

This obviously did not work, as msg is of type map[string]interface {} . My next attempt was then to use something like:

data := msg.Data.(map[string]interface {})

v ,exist := data["somestring"]
if exist {
    fmt.Println("somestring: ",v)
}

This works and is OK in this simply case, but in the real case there are many more than 2 structures, and they are themselves nested and fairly complicated.

The only approach that I managed to find was to do create multiple specific structures and use several unmarshalls like this:

type GenericHead struct {
    Type  string `json:"type"`
    Data interface{} `json:"data"`
}

type A struct {
    SomeNumber decimal.Decimal `json:"somenumber"`
}

type B struct {
    SomeString string `json:"somestring"`
    AnotherString string `json:"anotherstring"`
}

type SpecificA struct {
    Type  string `json:"type"`
    Data A `json:"data"`
}

type SpecificB struct {
    Type  string `json:"type"`
    Data B `json:"data"`
}

and then

var msg Head

json.Unmarshal([]byte(jsonString), &msg)

if msg.Type == "a_number" {
   var data SpecificA
   json.Unmarshal([]byte(jsonString), &data)
} else {
   var data SpecificA
   json.Unmarshal([]byte(jsonString), &data)
}

Having to define multiple (redundant) structures and unmarshalling several times seems very inefficient and unnecessarily complicated.

What is a more efficient, "best practice" approach to address such a sitution?

Thanks to the pointer from Cerise Limón an answer was already provided in Decoding generic JSON objects to one of many formats , and uses json.RawMessage as the solution:

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

and

var msg Head

json.Unmarshal([]byte(jsonString), &msg)

switch msg.Type {
    case "a_number":
        var detail A
        json.Unmarshal([]byte(msg.Data),&detail)
        fmt.Println(detail.SomeNumber)
    case "b_string":
        var detail B
        json.Unmarshal([]byte(msg.Data),&detail)
        fmt.Println(detail.SomeString)
    default:
        fmt.Printf("I don't know about type %s!\n",  msg.Type)
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM