Trouble unmarshaling text into struct - go

Trying to unmarshal a json text into my own struct. My struct definitions seem correct but json.Unmarshal doesn't return anything.
package main
import (
"encoding/json"
"fmt"
)
type CmdUnit struct {
Command string
Description string
}
type CmdList struct {
ListOfCommands []CmdUnit
}
type OneCmdList struct {
Area string
CmdList CmdList
}
type AllCommands struct {
AllAreas []OneCmdList
}
func main() {
jsonTxt := `
{
"Area1": [{
"Command": "cmd1",
"Desc": "cmd1 desc"
}, {
"Command": "cmd2",
"Desc": "cmd2 desc"
}],
"Area2": [{
"Command": "cmd1",
"Desc": "cmd1 desc"
}]
}
`
cmds := AllCommands{}
if err := json.Unmarshal([]byte(jsonTxt), &cmds); err != nil {
fmt.Println("Failed to unmarshal:", err)
} else {
fmt.Printf("%+v\n", cmds)
}
}
$ go run j.go
{AllAreas:[]}

Your structs have a different structure from the json you're providing. Marshalling the structs in your example would result in json that looks like:
{
"AllAreas": [
{
"Area": "Area1",
"CmdList": {
"ListOfCommands": [
{
"Command": "cmd1",
"Description": "cmd1 desc"
},
{
"Command": "cmd2",
"Description": "cmd2 desc"
}
]
}
}
]
}
The json in your example can be unmarshaled directly into a map[string][]CmdUnit{} with the minor change of CmdUnit.Description to CmdUnit.Desc.
cmds := map[string][]CmdUnit{}
if err := json.Unmarshal(jsonTxt, &cmds); err != nil {
log.Fatal("Failed to unmarshal:", err)
}
fmt.Printf("%+v\n", cmds)
https://play.golang.org/p/DFLYAfNLES

Related

How to create custom unmarshal for 2 types map[string]interface{} and []interface{}

Here's my sample go playground https://go.dev/play/p/MosQs62YPvI
My curl API return 2 kind of return, can any of the ff:
{
"code": 200,
"message": "Success",
"data": {
"list": {
"1": {
"user": "user A",
"status": "normal"
},
"2": {
"user": "user A",
"status": "normal"
}
},
"page": 1,
"total_pages": 2000
}
}
or
{
"code": 200,
"message": "Success",
"data": {
"list": [
{
"user": "user A",
"status": "normal"
},
{
"user": "user B",
"status": "normal"
}
],
"page": 1,
"total_pages": 5000
}
}
How to unmarshal it properly?
Here's my struct
type User struct {
Code int `json:"code"`
Message string `json:"message"`
Data struct {
List []struct {
User string `json:"user"`
Status string `json:"status"`
} `json:"list"`
Page int `json:"page"`
TotalPages int `json:"total_pages"`
} `json:"data"`
}
Here's how I unmarshal it
err = json.Unmarshal([]byte(io_response), &returnData)
if err != nil {
log.Println(err)
}
I have tried creating my own unmarshal but I have issues converting it to map[string]interface{}
Can you please help me? Or is there any better way?
type UserItem struct {
User string `json:"user"`
Status string `json:"status"`
}
type UserList []UserItem
func (ul *UserList) UnmarshalJSON(data []byte) error {
switch {
case len(data) == 0 || string(data) == `null`:
return nil
case data[0] == '[': // assume it's a JSON array
return json.Unmarshal(data, (*[]UserItem)(ul))
case data[0] == '{': // assume it's a JSON object
obj := make(map[string]UserItem)
if err := json.Unmarshal(data, &obj); err != nil {
return err
}
for _, v := range obj {
*ul = append(*ul, v)
}
return nil
default:
return fmt.Errorf("unsupported json type")
}
return nil
}
https://go.dev/play/p/Y5PAjrmPhy2

How to get the number of items from a structure sub field using reflect in go

I have some issues when getting the number of items from a sub field in a slice struct through reflect package.
This is how I'm trying to get the number of items from Items
func main() {
type Items struct {
Name string `json:"name"`
Present bool `json:"present"`
}
type someStuff struct {
Fields string `json:"fields"`
Items []Items `json:"items"`
}
type Stuff struct {
Stuff []someStuff `json:"stuff"`
}
some_stuff := `{
"stuff": [
{
"fields": "example",
"items": [
{ "name": "book01", "present": true },
{ "name": "book02", "present": true },
{ "name": "book03", "present": true }
]
}
]
}`
var variable Stuff
err := json.Unmarshal([]byte(some_stuff), &variable)
if err != nil {
panic(err)
}
//I want to get the number of items in my case 3
NumItems := reflect.ValueOf(variable.Stuff.Items)
}
This is the error:
variable.Items undefined (type []Stuff has no field or method Items)
I'm unsure if I can retrieve the number of items like that.
I have already fixed the issue.
In order to get the number of sub fields we can make use of Len() from reflect.ValueOf.
The code now is getting the number of Items:
package main
import (
"encoding/json"
"fmt"
"reflect"
)
func main() {
type Items struct {
Name string `json:"name"`
Present bool `json:"present"`
}
type someStuff struct {
Fields string `json:"fields"`
Items []Items `json:"items"`
}
type Stuff struct {
Stuff []someStuff `json:"stuff"`
}
some_stuff := `{
"stuff": [
{
"fields": "example",
"items": [
{ "name": "book01", "present": true },
{ "name": "book02", "present": true },
{ "name": "book03", "present": true }
]
}
]
}`
var variable Stuff
err := json.Unmarshal([]byte(some_stuff), &variable)
if err != nil {
panic(err)
}
//I want to get the number of items in my case 3
t := reflect.ValueOf(variable.Stuff[0].Items)
fmt.Println(t.Len())
}
Output: 3

Convert JSON key value pair into single field value in Go

I have a json like following, where value can be int or string
{
"data": [
{
"Name": "a_name",
"value": 1
},
{
"Name": "b_name",
"value": "val"
},
{
"Name": "c_name",
"value": 2
}
]
}
Now I want to convert that json into following struct, like only extract a_name and b_name value.
type Data struct {
AName int `json: "a_name"`
BName string `json: "b_name"`
}
I can do it by following way
import (
"encoding/json"
"fmt"
)
type TmpData struct {
Data []struct {
Name string `json:"Name"`
Value interface{} `json:"value"`
} `json:"data"`
}
type ExpectedData struct {
AName int `json: "a_name"`
BName string `json: "b_name"`
}
func main() {
data := `{
"data": [
{
"Name": "a_name",
"value": 1
},
{
"Name": "b_name",
"value": "val"
},
{
"Name": "c_name",
"value": 2
}
]
}`
tmpData := &TmpData{}
json.Unmarshal([]byte(data), tmpData)
ans := &ExpectedData{}
for _, d := range tmpData.Data {
if d.Name == "a_name" {
ans.AName = int(d.Value.(float64))
} else if d.Name == "b_name" {
ans.BName = d.Value.(string)
}
}
fmt.Println(ans)
}
Is there any better solution for this?
Not possible with the standard JSON un-marshalling unless you write a custom un-marshaller for your Data type.
The key here is to define the type for value to be an interface{}, so that multiple types could be stored in your b_name record.
func (d *Data) UnmarshalJSON(data []byte) error {
var result Details
if err := json.Unmarshal(data, &result); err != nil {
return err
}
for _, value := range result.Data {
switch value.Name {
// The json package will assume float64 when Unmarshalling with an interface{}
case "a_name":
v, ok := (value.Value).(float64)
if !ok {
return fmt.Errorf("a_name got data of type %T but wanted float64", value.Value)
}
d.AName = int(v)
case "b_name":
v, ok := (value.Value).(string)
if !ok {
return fmt.Errorf("b_name got data of type %T but wanted string", value.Value)
}
d.BName = v
}
}
return nil
}
Playground - https://go.dev/play/p/GrXKAE87d1F

golang unmarshal map[string]interface{} to a struct containing an array with meta

I have the following json data coming through an API. I want to unmarshal this data into a different way of structure as it is defined below. How can I do it in an elegant way?
{
"_meta": {
"count": 2,
"total": 2
},
"0": {
"key": "key0",
"name": "name0"
},
"1": {
"key": "key1",
"name": "name1"
},
"2": {
"key": "key2",
"name": "name2"
}
// It goes on..
}
type Data struct {
Meta Meta `json:"_meta,omitempty"`
Elements []Element
}
type Element struct {
Key string
Name string
}
type Meta struct{
Count int
Total int
}
This can be quite tricky because you have a json object that holds everything. So i went with the approach of unmarshalling to map of string to *json.RawMessage and then fixing the struct from there.
To do that you will be using a custom Unmarshaler and the benefit of it is that you delay the actual parsing of the inner messages until you need them.
So for example if your meta field was wrong or the numbers it said didn't match the length of the map-1 you could exit prematurely.
package main
import (
"encoding/json"
"fmt"
)
type jdata map[string]*json.RawMessage
type data struct {
Meta Meta
Elements []Element
}
//Element is a key val assoc
type Element struct {
Key string
Name string
}
//Meta holds counts and total of elems
type Meta struct {
Count int
Total int
}
var datain = []byte(`
{
"_meta": {
"count": 2,
"total": 2
},
"0": {
"key": "key0",
"name": "name0"
},
"1": {
"key": "key1",
"name": "name1"
},
"2": {
"key": "key2",
"name": "name2"
}
}`)
func (d *data) UnmarshalJSON(buf []byte) (err error) {
var (
meta *json.RawMessage
ok bool
)
jdata := new(jdata)
if err = json.Unmarshal(buf, jdata); err != nil {
return
}
if meta, ok = (*jdata)["_meta"]; !ok {
return fmt.Errorf("_meta field not found in JSON")
}
if err = json.Unmarshal(*meta, &d.Meta); err != nil {
return
}
for k, v := range *jdata {
if k == "_meta" {
continue
}
elem := &Element{}
if err = json.Unmarshal(*v, elem); err != nil {
return err
}
d.Elements = append(d.Elements, *elem)
}
return nil
}
func main() {
data := &data{}
if err := data.UnmarshalJSON(datain); err != nil {
panic(err)
}
fmt.Printf("decoded:%v\n", data)
}

go + elastigo panic: runtime error: index out of range

I'm testing golang with elasticsearch
I'm using the library:
https://github.com/mattbaird/elastigo
My problem is when i run:
go run elastigo_postal_code2.go
The compiler show something like this:
panic: runtime error: index out of range
goroutine 1 [running]:
panic(0x893ce0, 0xc82000a150)
/opt/go/src/runtime/panic.go:464 +0x3ff
main.main()
/home/hector/go/elastigo_postal_code2.go:80 +0xa30
exit status 2
I'm not sure what this means or how I can fix it!
Someone can help me and tell me I'm doing wrong
/*
curl -X PUT "http://localhost:9200/mx2/postal_code/1" -d "
{
\"cp\" : \"20000\",
\"colonia\" : \"Zona Centro\",
\"ciudad\" : \"Aguascalientes\",
\"delegacion\" : \"Aguascalientes\",
\"location\": {
\"lat\": \"22.0074\",
\"lon\": \"-102.2837\"
}
}"
curl -X PUT "http://localhost:9200/mx2/postal_code/2" -d "
{
\"cp\" : \"20008\",
\"colonia\" : \"Delegacion de La Secretaria de Comercio y Fomento Industrial\",
\"ciudad\" : \"Aguascalientes\",
\"delegacion\" : \"Aguascalientes\",
\"location\": {
\"lat\": \"22.0074\",
\"lon\": \"-102.2837\"
}
}"
*/
package main
import (
"encoding/json"
"flag"
"log"
elastigo "github.com/mattbaird/elastigo/lib"
)
var (
eshost *string = flag.String("host", "localhost", "Elasticsearch Server Host Address")
)
func main() {
flag.Parse()
log.SetFlags(log.Ltime | log.Lshortfile)
c := elastigo.NewConn()
c.Domain = *eshost
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
searchJson := `{
"size": 10,
"query": {
"match": {
"all": {
"query": "aguascalientes",
"operator": "and"
}
}
},
"sort": [{
"colonia": {
"order": "asc",
"mode": "avg"
}
}]
}`
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
searchresponse, err := c.Search("mx2", "postal_code", nil, searchJson)
if err != nil {
log.Println("error during search:" + err.Error())
log.Fatal(err)
}
// try marshalling to ElasticSearchResponse type
var t ElasticSearchResponse
bytes, err := searchresponse.Hits.Hits[0].Source.MarshalJSON()
if err != nil {
log.Fatalf("err calling marshalJson:%v", err)
}
json.Unmarshal(bytes, &t)
log.Printf("Search Found: %s", t)
c.Flush()
}
func (t *ElasticSearchResponse) String() string {
b, _ := json.Marshal(t)
return string(b)
}
// used in test suite, chosen to be similar to the documentation
type ElasticSearchResponse struct {
Cp string `json:"cp"`
Colonia string `json:"colonia"`
Ciudad string `json:"ciudad"`
Delegacion string `json:"delegacion"`
Location Location `json:"location"`
}
type Location struct {
Lat string `json:"lat"`
Lon string `json:"lon"`
}
The code of my scritp is here:
https://gist.github.com/hectorgool/c9e18d7d6324a9ed1a2df92ddcc95c08#file-elastigo_example-go-L80
That's not a compile error, you are getting panic: runtime error: index out of range
If the slice searchresponse.Hits.Hits has a length of 0, indexing the first element is going to be out of range and panic. You can check for the number of Hits first with searchresponse.Hits.Len().

Resources