How do I json unmarshal slice inside a slice - json

I am trying to unmarshal some pretty ugly json but can't figure out how. I have:
package main
import "fmt"
import "encoding/json"
type PublicKey struct {
ID int `json:"id"`
Key string `json:"key"`
MyData []struct {
ID string `json:"id"`
Value int `json:"value"`
}
}
func main() {
b := `[
{
"id": 1,
"key": "my_key"
},
[
{
"id": "some_id",
"value": 12
},
{
"id": "anorther_id",
"value": 13
}
]
]`
var pk []PublicKey
err := json.Unmarshal([]byte(b), &pk)
if err != nil {
fmt.Println(err)
}
fmt.Println(pk)
}
For the result I am getting:
[{1 my_key []} {0 []}]
The second slice is empty when it shouldn't be.
EDIT:
The error I get is:
json: cannot unmarshal array into Go struct field PublicKey.key of type main.PublicKey
https://play.golang.org/p/cztXOchiiS5

That is some truly hideous JSON! I have two approaches to handling the mixed array elements and I like the 2nd one better. Here's the first approach using interface and a type switch:
package main
import (
"encoding/json"
"errors"
"fmt"
)
type PublicKey struct {
ID int `json:"id"`
Key string `json:"key"`
}
type MyData struct {
ID string `json:"id"`
Value int `json:"value"`
}
type MixedData struct {
Key []PublicKey
MyData [][]MyData
}
func (md *MixedData) UnmarshalJSON(b []byte) error {
md.Key = []PublicKey{}
md.MyData = [][]MyData{}
var obj []interface{}
err := json.Unmarshal([]byte(b), &obj)
if err != nil {
return err
}
for _, o := range obj {
switch o.(type) {
case map[string]interface{}:
m := o.(map[string]interface{})
id, ok := m["id"].(float64)
if !ok {
return errors.New("public key id must be an int")
}
pk := PublicKey{}
pk.ID = int(id)
pk.Key, ok = m["key"].(string)
if !ok {
return errors.New("public key key must be a string")
}
md.Key = append(md.Key, pk)
case []interface{}:
a := o.([]interface{})
myData := make([]MyData, len(a))
for i, x := range a {
m, ok := x.(map[string]interface{})
if !ok {
return errors.New("data array contains unexpected object")
}
val, ok := m["value"].(float64)
if !ok {
return errors.New("data value must be an int")
}
myData[i].Value = int(val)
myData[i].ID, ok = m["id"].(string)
if !ok {
return errors.New("data id must be a string")
}
md.MyData = append(md.MyData, myData)
}
default:
// got something unexpected, handle somehow
}
}
return nil
}
func main() {
b := `[
{
"id": 1,
"key": "my_key"
},
[
{
"id": "some_id",
"value": 12
},
{
"id": "another_id",
"value": 13
}
]
]`
m := MixedData{}
err := json.Unmarshal([]byte(b), &m)
if err != nil {
fmt.Println(err)
}
fmt.Println(m)
}
https://play.golang.org/p/g8d_AsH-pYY
Hopefully there aren't any unexpected other elements, but they can be handled similarly.
Here is the second that relies more on Go's internal JSON parsing with the help of json.RawMessage. It makes the same assumptions about the contents of the array. It assumes that any objects will Unmarshal into PublicKey instances and any arrays consist of only MyData instances. I also added how to marshal back into the target JSON for symmetry:
package main
import (
"encoding/json"
"fmt"
"os"
)
type PublicKey struct {
ID int `json:"id"`
Key string `json:"key"`
}
type MyData struct {
ID string `json:"id"`
Value int `json:"value"`
}
type MixedData struct {
Keys []PublicKey
MyData [][]MyData
}
func (md *MixedData) UnmarshalJSON(b []byte) error {
md.Keys = []PublicKey{}
md.MyData = [][]MyData{}
obj := []json.RawMessage{}
err := json.Unmarshal([]byte(b), &obj)
if err != nil {
return err
}
for _, o := range obj {
switch o[0] {
case '{':
pk := PublicKey{}
err := json.Unmarshal(o, &pk)
if err != nil {
return err
}
md.Keys = append(md.Keys, pk)
case '[':
myData := []MyData{}
err := json.Unmarshal(o, &myData)
if err != nil {
return err
}
md.MyData = append(md.MyData, myData)
default:
// got something unexpected, handle somehow
}
}
return nil
}
func (md *MixedData) MarshalJSON() ([]byte, error) {
out := make([]interface{}, len(md.Keys)+len(md.MyData))
i := 0
for _, x := range md.Keys {
out[i] = x
i++
}
for _, x := range md.MyData {
out[i] = x
i++
}
return json.Marshal(out)
}
func main() {
b := `[
{
"id": 1,
"key": "my_key"
},
[
{
"id": "some_id",
"value": 12
},
{
"id": "another_id",
"value": 13
}
]
]`
m := MixedData{}
err := json.Unmarshal([]byte(b), &m)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Println(m)
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
if err := enc.Encode(m); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
https://play.golang.org/p/ryZzaWKNcN0

Here's an approach that combines json.RawMessage with the trick of using the default unmarshaler in a type that implements json.Unmarshaler by creating a new temporary type that aliases the target type.
The idea is that we unmarshal the incoming array into a raw message and ensure that the array length is what we expect. Then we unmarshal the individual array elements into the custom struct types using their JSON tag annotations. The end result is that we can unmarshal the PublicKey type in the usual way and the UnmarshalJSON code is not terribly difficult to follow once you understand the tricks.
For example (Go Playground):
type PublicKey struct {
ID int `json:"id"`
Key string `json:"key"`
Data []MyData
}
type MyData struct {
ID string `json:"id"`
Value int `json:"value"`
}
func (pk *PublicKey) UnmarshalJSON(bs []byte) error {
// Unmarshal into a RawMessage so we can inspect the array length.
var rawMessage []json.RawMessage
err := json.Unmarshal(bs, &rawMessage)
if err != nil {
return err
}
if len(rawMessage) != 2 {
return fmt.Errorf("expected array of length 2, got %d", len(rawMessage))
}
// Parse the first object as PublicKey using the default unmarshaler
// using a temporary type that is an alias for the target type.
type PublicKey2 PublicKey
var pk2 PublicKey2
err = json.Unmarshal(rawMessage[0], &pk2)
if err != nil {
return err
}
// Parse the second object as []MyData in the usual way.
err = json.Unmarshal(rawMessage[1], &pk2.Data)
if err != nil {
return err
}
// Finally, assign the aliased object to the target object.
*pk = PublicKey(pk2)
return nil
}
func main() {
var pk PublicKey
err := json.Unmarshal([]byte(jsonstr), &pk)
if err != nil {
panic(err)
}
fmt.Printf("%#v\n", pk)
// main.PublicKey{ID:1, Key:"my_key", Data:[]main.MyData{main.MyData{ID:"some_id", Value:12}, main.MyData{ID:"anorther_id", Value:13}}}
}

Related

How to handle unmarshaling to a custom interface whose type could only be determined after unmarshaling

I have a json response like this
{
"foo" : "bar",
"object" : {
"type" : "action",
"data" : "somedata"
}
}
Here the object could be one of multiple types. I define the types and have them implement a common interface.
type IObject interface {
GetType() string
}
type Action struct {
Type string `json:"type"`
Data string `json:"data"`
}
func (a Action) GetType() string {
return "action"
}
type Activity struct {
Type string `json:"type"`
Duration int `json:"duration"`
}
func (a Activity) GetType() string {
return "activity"
}
And a response struct
type Response struct {
Foo string `json:"foo"`
Object IObject `json:"object"`
}
As the type information of a struct that implements IObject is contained within the struct, there is no way to learn in without unmarshaling. I also cannot change the structure of the json response. Currently I am dealing with this problem using a custom unmarshaller:
func UnmarshalObject(m map[string]interface{}, object *IObject) error {
if m["type"] == "action" {
b, err := json.Marshal(m)
if err != nil {
return err
}
action := Action{}
if err = json.Unmarshal(b, &action); err != nil {
return err
}
*object = action
return nil
}
if m["type"] == "activity" {
b, err := json.Marshal(m)
if err != nil {
return err
}
activity := Activity{}
if err = json.Unmarshal(b, &activity); err != nil {
return err
}
*object = activity
return nil
}
return errors.New("unknown actor type")
}
func (r *Response) UnmarshalJSON(data []byte) error {
raw := struct {
Foo string `json:"foo"`
Object interface{} `json:"object"`
}{}
err := json.Unmarshal(data, &raw)
if err != nil {
return err
}
r.Foo = raw.Foo
if err = UnmarshalObject(raw.Object.(map[string]interface{}), &r.Object); err != nil
{
return err
}
return nil
}
So what I do is basically
Unmarshall the object into an interface{}
Typecast to map[string]interface{}
Read the "type" value to determine the type
Create a new instance of the determined type
Marshal back to json
Unmarshal again to the new instance of the determined type
Assign the instance to the field
This feels off and I am not comfortable with it. Especially the marshaling/unmarshaling back and forth. Is there a more elegant way to solve this problem?
You can use json.RawMessage.
func (r *Response) UnmarshalJSON(data []byte) error {
var raw struct {
Foo string `json:"foo"`
Object json.RawMessage `json:"object"`
}
if err := json.Unmarshal(data, &raw); err != nil {
return err
}
r.Foo = raw.Foo
var obj struct {
Type string `json:"type"`
}
if err := json.Unmarshal(raw.Object, &obj); err != nil {
return err
}
switch obj.Type {
case "action":
r.Object = new(Action)
case "activity":
r.Object = new(Activity)
}
return json.Unmarshal(raw.Object, r.Object)
}
https://go.dev/play/p/6dqiybS4zNp

Custom UnmarshalJSON: array object to map

I have json files like this:
{
"Systems":[
{
"ID":74,
"Data1":0.1,
"Data2":4
},
{
"ID":50,
"Data1":31,
"Data2":3
}
],
"Error":false
}
I would like to unmarshal in Go to something like this (note map):
type Info struct {
Systems map[int]System `json:"Systems"` // key should be ID json field
Error bool `json:"Error"`
}
type System struct {
Data1 float32 `json:"Data1"`
Data2 int `json:"Data2"`
}
Here is my (wrong) code:
package main
import (
"encoding/json"
"fmt"
)
type Info struct {
Systems map[int]System `json:"Systems"` // key should be ID json field
Error bool `json:"Error"`
}
type System struct {
ID int `json:"ID"`
Data1 float32 `json:"Data"`
Data2 int `json:"Data2"`
}
func main() {
file := "{\"Systems\":[{\"ID\":74,\"Data1\":0.1,\"Data2\":4},{\"ID\":50,\"Data1\":31,\"Data2\":3}],\"Error\":true}"
info := Info{}
bytes := []byte(file)
err := json.Unmarshal(bytes, &info)
if err != nil {
fmt.Printf("=> %v\n", err)
}
fmt.Printf("INFO: %+v\n", info)
}
func (d *Info) UnmarshalJSON(buf []byte) error {
var tmp interface{}
if err := json.Unmarshal(buf, &tmp); err != nil {
return err
}
d.Error = tmp.(map[string]interface{})["Error"].(bool)
d.Systems = make(map[int]System)
for _, v := range tmp.(map[string]interface{})["Systems"].([]interface{}) {
d.Systems[v.(map[string]interface{})["ID"].(int)] = v.(map[string]interface{}).(System)
}
return nil
}
https://play.golang.org/p/L_Gx-f9ycjW
You can try this
package main
import (
"encoding/json"
"fmt"
)
func main() {
var str = `{
"Systems":[
{
"ID":74,
"Data1":0.1,
"Data2":4
},
{
"ID":50,
"Data1":31,
"Data2":3
}
],
"Error":false
}`
var t Info
err := json.Unmarshal([]byte(str), &t)
if err != nil {
panic(err)
}
bts, err := json.MarshalIndent(t, "", " ")
if err != nil {
panic(err)
}
fmt.Println(string(bts))
}
type Info struct {
Systems map[int]System `json:"Systems"`
Error bool `json:"Error"`
}
type System struct {
ID int `json:"ID,omitempty"`
Data1 float32 `json:"Data1"`
Data2 int `json:"Data2"`
}
func (info *Info) UnmarshalJSON(data []byte) error {
var t struct {
Systems []System `json:"Systems"`
Error bool `json:"Error"`
}
err := json.Unmarshal(data, &t)
if err != nil {
return err
}
info.Systems = make(map[int]System, 0)
for _, v := range t.Systems {
info.Systems[v.ID] = v
}
return nil
}
https://play.golang.org/p/qB3vF08cmW8
Output:
{
"Systems": {
"50": {
"ID": 50,
"Data1": 31,
"Data2": 3
},
"74": {
"ID": 74,
"Data1": 0.1,
"Data2": 4
}
},
"Error": false
}
You can't decode json directly in the struct you proposed because it doesn't match the json structure.
What you can do is decoding the json into this:
type Info struct {
Systems []*System `json:"Systems"` // array here
Error bool `json:"Error"`
Index map[int]*System // not json mapped
}
type System struct {
ID int `json:"ID"`
Data1 float32 `json:"Data1"`
Data2 int `json:"Data2"`
}
and populate the Index field in postprocessing with something like this:
var info Info
json.Unmarshal(dataIn, &info)
info.Index = map[int]*System{} // initialize an empty map
for _, s := range info.Systems {
info.Index[s.ID] = s
}
fmt.Println(info.Index[50].Data1)
you can find a full example here https://play.golang.org/p/B8O6nfI258-

How to Unmarshal an inconsistent JSON field that can be a string *or* an array of string?

I am having trouble Unmarshalling some Json I don't have control over.
There is one field that 99% of the time is a string but occasionally is an array.
type MyListItem struct {
Date string `json:"date"`
DisplayName string `json:"display_name"`
}
type MyListings struct {
CLItems []MyListItem `json:"myitems"`
}
var mylist MyListings
err = json.Unmarshal(jsn, &mylist)
if err != nil {
fmt.Print("JSON:\n%s\n error:%v\n", string(jsn),err)
return
}
Json is as follows:
{
"date": "30 Apr",
"display_name": "Mr Smith"
},
{
"date": "30 Apr",
"display_name": ["Mr Smith", "Mr Jones"],
}
error: json: cannot unmarshal array into Go struct field MyListItem.display_name of type string
Use json.RawMessage to capture the varying field.
Use the json "-" name to hide the DisplayName field from decoder. The application will fill this field after the top-level JSON is decoded.
type MyListItem struct {
Date string `json:"date"`
RawDisplayName json.RawMessage `json:"display_name"`
DisplayName []string `json:"-"`
}
Unmarshal the top-level JSON:
var li MyListItem
if err := json.Unmarshal(data, &li); err != nil {
// handle error
}
Unmarshal the display name depending on the type of the raw data:
if len(li.RawDisplayName) > 0 {
switch li.RawDisplayName[0] {
case '"':
if err := json.Unmarshal(li.RawDisplayName, &li.DisplayName); err != nil {
// handle error
}
case '[':
var s []string
if err := json.Unmarshal(li.RawDisplayName, &s); err != nil {
// handle error
}
// Join arrays with "&" per OP's comment on the question.
li.DisplayName = strings.Join(s, "&")
}
}
playground example
Incorporate the above into a for loop to handle MyListings:
var listings MyListings
if err := json.Unmarshal([]byte(data), &listings); err != nil {
// handle error
}
for i := range listings.CLItems {
li := &listings.CLItems[i]
if len(li.RawDisplayName) > 0 {
switch li.RawDisplayName[0] {
case '"':
if err := json.Unmarshal(li.RawDisplayName, &li.DisplayName); err != nil {
// handle error
}
case '[':
var s []string
if err := json.Unmarshal(li.RawDisplayName, &s); err != nil {
// handle error
}
li.DisplayName = strings.Join(s, "&")
}
}
}
playground example
If there's more than one place in the data model where a value can be a string or []string, it can be helpful to encapsulate the logic in a type. Parse the JSON data in an implementation of the json.Unmarshaler interface.
type multiString string
func (ms *multiString) UnmarshalJSON(data []byte) error {
if len(data) > 0 {
switch data[0] {
case '"':
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
*ms = multiString(s)
case '[':
var s []string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
*ms = multiString(strings.Join(s, "&"))
}
}
return nil
}
Use it like this:
type MyListItem struct {
Date string `json:"date"`
DisplayName multiString `json:"display_name"`
}
type MyListings struct {
CLItems []MyListItem `json:"myitems"`
}
var listings MyListings
if err := json.Unmarshal([]byte(data), &listings); err != nil {
log.Fatal(err)
}
Playground Example
Here's the code to get the value as a slice of strings instead of as a single string with values joined by &.
type multiString []string
func (ms *multiString) UnmarshalJSON(data []byte) error {
if len(data) > 0 {
switch data[0] {
case '"':
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
*ms = multiString{s}
case '[':
if err := json.Unmarshal(data, (*[]string)(ms)); err != nil {
return err
}
}
}
return nil
}
Playground example.
As an alternative, this builds off of the answer from #ThunderCat but instead of using json.RawMessage, uses interface{} and a type switch:
package main
import (
"encoding/json"
"fmt"
"log"
)
type MyListItem struct {
Date string `json:"date"`
DisplayName string `json:"-"`
RawDisplayName interface{} `json:"display_name"`
}
func (li *MyListItem) UnmarshalJSON(data []byte) error {
type localItem MyListItem
var loc localItem
if err := json.Unmarshal(data, &loc); err != nil {
return err
}
*li = MyListItem(loc)
switch li.RawDisplayName.(type) {
case string:
li.DisplayName = li.RawDisplayName.(string)
case []interface{}:
vals := li.RawDisplayName.([]interface{})
if len(vals) > 0 {
li.DisplayName, _ = vals[0].(string)
for _, v := range vals[1:] {
li.DisplayName += "&" + v.(string)
}
}
}
return nil
}
func test(data string) {
var li MyListItem
if err := json.Unmarshal([]byte(data), &li); err != nil {
log.Fatal(err)
}
fmt.Println(li.DisplayName)
}
func main() {
test(`
{
"date": "30 Apr",
"display_name": "Mr Smith"
}`)
test(`
{
"date": "30 Apr",
"display_name": ["Mr Smith", "Mr Jones"]
}`)
}
playground

How do I decode the following JSON?

I have a JSON object of the format
{
"results": [
{
"hits": [
{
"title": "Juliette DELAUNAY",
"author:url": "abc.com"
}
]
}
]
}
To decode in my go program, I have made the following structs
type results struct{
Result []result `json:"results"`
}
type result struct{
Hits []hit `json:"hits"`
}
type hit struct{
Name string `json:"title"`
Url string `json:"author:url"`
}
var m =make(map[string]string)
var t results
But when I try to do the following,
decoder := json.NewDecoder(resp.Body)
err = decoder.Decode(&t)
if err != nil {
fmt.Println(err)
}
for _,x := range t.Result[0].Hits{
m[x.Name] = x.Url
fmt.Println(x.Name,x.Url)
}
It gives a runtime error saying index is out of range. What am I doing wrong? Are my structs incorrect for the given json?
EDIT : The JSON file I need to decode
var jsonStr = []byte(`{"requests":[{"indexName":"recherchepepitesAtoZ","params":"query=x&hitsPerPage=2817&maxValuesPerFacet=42&page=0&facets=%5B%22field_frenchtech_hub_pepite%22%2C%22field_categorie%22%2C%22field_frenchtech_hub_pepite%22%5D&tagFilters="}]}`)
req, err := http.NewRequest("POST", "http://6y0slgl8yj-dsn.algolia.net/1/indexes/*/queries?x-algolia-agent=Algolia%20for%20vanilla%20JavaScript%20(lite)%203.20.4%3Binstantsearch.js%201.10.4%3BJS%20Helper%202.18.0&x-algolia-application-id=6Y0SLGL8YJ&x-algolia-api-key=6832a361e1e1628f8ddb2483623104c6", bytes.NewBuffer(jsonStr))
//req.Header.Set("X-Custom-Header", "application/x-www-form-urlencoded")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
Here is a slightly modified version that works on my machine and go playground:
GoPlayground
package main
import (
"encoding/json"
"fmt"
"strings"
)
type results struct {
Result []result `json:"results"`
}
type result struct {
Hits []hit `json:"hits"`
}
type hit struct {
Name string `json:"title"`
Url string `json:"author:url"`
}
var m = make(map[string]string)
func main() {
jsonSample := `{
"results": [
{
"hits": [
{
"title": "Juliette DELAUNAY",
"author:url": "abc.com"
}
]
}
]
}`
var t results
decoder := json.NewDecoder(strings.NewReader(jsonSample))
err := decoder.Decode(&t)
if err != nil {
fmt.Println(err)
}
for _, x := range t.Result[0].Hits {
m[x.Name] = x.Url
fmt.Println(x.Name, x.Url)
}
}

anonymous fields in JSON

I'm reverse engineering some JSON that seems to be using anonymous field names. For example:
{
"1": 123,
"2": 234,
"3": 345
}
BTW - it's not simply using "1" and "2" and "3" because they represent userids that are at a minimum int32's.
Is there some way such as using tags to properly Unmarshal the JSON?
I've tried:
package main
import (
"encoding/json"
"fmt"
)
type MyStruct struct {
string `json:",string"`
}
func main() {
jsonData := []byte("{\"1\":123,\"2\":234,\"3\":345}")
var decoded MyStruct
err := json.Unmarshal(jsonData, &decoded)
if err != nil {
panic(err)
}
fmt.Printf("decoded=%+v\n", decoded)
}
Just decode the data into a map (map[string]int):
jsonData := []byte("{\"1\":123,\"2\":234,\"3\":345}")
var decoded map[string]int
err := json.Unmarshal(jsonData, &decoded)
if err != nil {
panic(err)
}
You'll then be able to iterate over and access the elements by the user ID key:
for userID, _ := range decoded {
fmt.Printf("User ID: %s\n", userID)
}
https://play.golang.org/p/SJkpahGzJY