Given the following struct types, StructA and StructB that are embedded in CompleteStruct
type StructA struct {
A int `json:"a_a"`
B int `json:"a_b"`
C int `json:"a_c"`
}
type StructB struct {
A int `json:"b_a"`
B int `json:"b_b"`
}
type CompleteStruct struct {
Name string `json:"name"`
StructA
StructB
}
And s which is a new struct.
s := CompleteStruct{Name: "Example",
StructA: StructA{
A: 1,
B: 2,
C: 3,
},
StructB: StructB{
A: 4,
B: 5,
},
}
How do you transform s to the following json.
[
{
"name": "Example",
"field": "a_a",
"value": 1
},
{
"name": "Example",
"field": "a_b",
"value": 2
},
{
"name": "Example",
"field": "a_c",
"value": 3
},
{
"name": "Example",
"field": "b_a",
"value": 4
},
{
"name": "Example",
"field": "b_b",
"value": 5
}
]
Note: In reality, CompleteStruct will contain 10 or more embedded structs and each embedded struct will contain 10 or more fields. So I would like a solution that does not require typing each field out individually, I assume this will require using reflection
You can't solve it without reflection. Simple example:
func (u *CompleteStruct) MarshalJSON() ([]byte, error) {
type Result struct {
Name string `json:"name"`
Field string `json:"field"`
Value any `json:"value"`
}
var res []Result
val := reflect.ValueOf(u).Elem()
for i := 0; i < val.NumField(); i++ {
field := val.Field(i)
switch field.Kind() {
case reflect.Struct:
for i := 0; i < field.NumField(); i++ {
tp := field.Type().Field(i)
field := field.Field(i)
res = append(res, Result{
Name: u.Name,
Field: tp.Tag.Get("json"),
Value: field.Interface(),
})
}
}
}
return json.Marshal(res)
}
PLAYGROUND
This should give you the structure you want:
package main
import (
"encoding/json"
"os"
"reflect"
)
type StructA struct {
A int `json:"a_a"`
B int `json:"a_b"`
C int `json:"a_c"`
}
type StructB struct {
A int `json:"b_a"`
B int `json:"b_b"`
}
type CompleteStruct struct {
Name string `json:"name"`
StructA
StructB
}
func main() {
s := CompleteStruct{Name: "Example",
StructA: StructA{
A: 1,
B: 2,
C: 3,
},
StructB: StructB{
A: 4,
B: 5,
},
}
flat(s)
json.NewEncoder(os.Stdout).Encode(results)
}
type resp struct {
Name string `json:"name"`
Field string `json:"field"`
Value any `json:"value"`
}
var globalName string
var results []resp
func flat(s interface{}) {
st := reflect.TypeOf(s)
for i := 0; i < st.NumField(); i++ {
field := st.Field(i)
if field.Type.Kind() == reflect.Struct {
flat(reflect.ValueOf(s).Field(i).Interface())
} else {
name := field.Tag.Get("json")
if name == "name" {
globalName = reflect.ValueOf(s).Field(i).String()
continue
}
results = append(results, resp{Name: globalName, Field: name, Value: reflect.ValueOf(s).Field(i).Interface()})
}
}
}
go run ./main.go | jq '.'
Go Playground
Related
I want get informations in JSON-RPC file with this structure :
{
"id": "foo1",
"error": null,
"result": [
{
"key": [
"hello 1",
1,
"world 1"
],
"val": {
"type": "static"
}
},
{
"key": [
"hello 2",
1,
"world 2"
],
"val": {
"type": "static"
}
}
]
}
This is my parsing function, Key is string table (can't accept int type) :
type JsonRpcRsp struct {
Id string `json:"id"`
Error *string `json:"error"`
Result json.RawMessage `json:"result"`
}
type JsonRpcEntry_Val struct {
Type string `json:"type"`
}
type JsonRpcEntry struct {
Key [3]string `json:"key"`
Val JsonRpcEntry_Val `json:"val"`
}
jsonResult := JsonRpcRsp{}
json.Unmarshal(data, &jsonResult)
entries := []JsonRpcEntry{}
for _, val := range jsonResult {
json.Unmarshal(val.Result, &entries)
}
How to parse "key" table ?... problem is there are different types
key table structure is :
[ <string>, <int>, <string>]
To unmarshal arrays of different types in Go you'll need to use interfaces and consequently type assertions if you need access to the types.
This will work for you:
type Result struct {
Key [3]interface{} `json:"key"`
Val struct {
Type string `json:"type"`
} `json:"val"`
}
msg := JsonRpcRsp{}
json.Unmarshal(data, &msg)
var result []Result
json.Unmarshal(msg.Result, &result)
for _, v := range result {
key1 := v.Key[0].(string)
key2 := v.Key[1].(float64)
key3 := v.Key[2].(string)
fmt.Println(key1, key2, key3)
}
After asserting the three interfaces to their types, you can then work with them further, depending on your use case.
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
I have an array of objects that I would like to use to create a new slice while grouping a field for objects with the same id(the id, in this case, the id is pay_method_id) into an array of objects. i.e I want to group all users with a particular payment method
sample input
[{
"user_id": 1,
"pay_method_id": 1
}
{
"user_id": 2,
"pay_method_id": 2
}
{
"user_id": 4,
"pay_method_id": 1
}
{
"user_id": 3,
"pay_method_id": 2
}]
expected output
[
{"pay_method_id" : "2",
"users": [{"user_id": 2}, {"user_id": 3}]
}
{
"pay_method_id" : "1",
"users": [{"user_id": 4}, {"user_id": 1}]
}
]
Struct representing input
type paymenthodUsers struct{
PayMethodID int
UserID int
}
Struct for the output
type paymentMethodGrouped struct{
PayMethodID int
Users []user
}
type user struct{
UserID int
}
How do I generate the expected output above in golang?
package main
import (
"encoding/json"
"fmt"
"log"
)
type paymenthodUsers struct {
PayMethodID int `json:"pay_method_id"`
UserID int `json:"user_id"`
}
type paymentMethodGrouped struct {
PayMethodID int `json:"pay_method_id"`
Users []user `json:"users"`
}
type user struct {
UserID int `json:"user_id"`
}
func main() {
_json := `[{
"user_id": 1,
"pay_method_id": 1
},
{
"user_id": 2,
"pay_method_id": 2
},
{
"user_id": 4,
"pay_method_id": 1
},
{
"user_id": 3,
"pay_method_id": 2
}]`
var paymentmethods []paymenthodUsers
err := json.Unmarshal([]byte(_json), &paymentmethods)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Original input : %+v\n", paymentmethods)
groups := make(map[int][]user)
for _, pm := range paymentmethods {
if _, found := groups[pm.PayMethodID]; !found {
groups[pm.PayMethodID] = []user{}
}
groups[pm.PayMethodID] = append(groups[pm.PayMethodID], user{pm.UserID})
}
paymentGroups := []paymentMethodGrouped{}
for k, v := range groups {
paymentGroups = append(paymentGroups, paymentMethodGrouped{k, v})
}
data, err := json.Marshal(paymentGroups)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Grouped: %s", string(data))
}
Go Playground Demo
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
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)
}