I am still learning GO and I am stumped by the following problem: I receive a JSON string that I want to unmarshal in GO. The JSON looks like this
{
"MAINKEY": {
"key1": 1,
"key2": [1, 2]
}
}
I am only interested in the contents of the MAINKEY, but similar to this question, I do not know the names of the keys, which should reflect the names of the map.
In the end, I want to have the following object:
type Result struct {
Key string
Value []int
}
expectedResult := []Result{
{"key1", []int{1}},
{"key2", []int{1, 2}},
}
fmt.Printf("WANT: % v\n", expectedResult)
//> WANT: [{Key:key1 Value:[1]} {Key:key2 Value:[1 2]}]
If possible, I don't want to unmarshal into a map[string]interface{}
first (but if there is no other way, that would be ok too).
Full code so far is:
package main
import (
"encoding/json"
"fmt"
)
// does produce expected result at the moment...
type Result struct {
Key string
Value []int
}
type Outer struct {
Key Result `json:"MAINKEY"`
}
func main() {
input := `{"MAINKEY": {"key1": 1, "key2": [1, 2]}}`
var cont Outer
json.Unmarshal([]byte(input), &cont)
fmt.Printf("GOT: % v\n", cont)
expectedResult := []Result{
{"key1", []int{1}},
{"key2", []int{1, 2}},
}
fmt.Printf("WANT: % v\n", expectedResult)
}
CodePudding user response:
You can use a custom unmarshaler with a map:
type ResultList []Result
func (ls *ResultList) UnmarshalJSON(data []byte) error {
var obj map[string]json.RawMessage
if err := json.Unmarshal(data, &obj); err != nil {
return err
}
for key, raw := range obj {
r := Result{Key: key}
if raw[0] == '[' { // assume array of ints
if err := json.Unmarshal(raw, &r.Value); err != nil {
return err
}
} else { // assume single int
var i int
if err := json.Unmarshal(raw, &i); err != nil {
return err
}
r.Value = append(r.Value, i)
}
*ls = append(*ls, r)
}
return nil
}
https://go.dev/play/p/Epd6cLwyWUm
Or, if you need to retain the order, you can tokenize the input:
type ResultList []Result
func (ls *ResultList) UnmarshalJSON(data []byte) error {
d := json.NewDecoder(bytes.NewReader(data))
i := -1
for {
t, err := d.Token()
if err == io.EOF {
break
}
if err != nil {
return err
}
switch v := t.(type) {
case string:
*ls = append(*ls, Result{Key: v})
i = 1
case float64:
(*ls)[i].Value = append((*ls)[i].Value, int(v))
}
}
return nil
}