I have a struct in Go that came from and XML resp body:
{
"pdp":{
"sellableUnits":[
{
"attributes":[
{
"id":"22555278",
"type":"size",
"value":"03.5"
}
]
}
]
}
}
type sizeJ struct {
PDP struct {
SellableUnits []struct {
Attributes []struct {
ID string `json:"id"`
Type string `json:"type"`
Val string `json:"value"`
} `json:"attributes"`
} `json:"units"`
} `json:"pdp"`
}
There are different Vals and a different ID depending on the value of Val.
Use a for loop, with range if you like.
func getValByID(j sizeJ, id string) string {
for _, u := range j.PDP.SellableUnits {
for _, a := range u.Attributes {
if a.ID == id {
return a.Val
}
}
}
return ""
}
func getIDByVal(j sizeJ, val string) string {
for _, u := range j.PDP.SellableUnits {
for _, a := range u.Attributes {
if a.Val == val {
return a.ID
}
}
}
return ""
}
https://play.golang.org/p/LjPrs1yGKGc
you can use map instead of nested struct,such as
var myMap map[string]map[string]interface{}
or
var myMap map[string]interface{}
or every thing you want.
Related
How do i use the following struct to build a nested json recursively?
The nested struct can go to as many levels as it can.
Both the sample struct and json is mentioned below.
I'm having trouble in building a nested json/object dynamically.
I used the reflect package to access the struct.
I'm able to read through the data but not able to build the same.
type Data struct {
ID string
Name string
Types *Details
}
type Details struct {
Customer int32
Countries int32
}
To:
{
"Name":"Data",
"Fields":[{
"Name":"ID",
"Type":"string"
},
{
"Name":"Name",
"Type":"string"
},
{
"Name":"Details",
"Type":"struct",
"Fields":[{
"Name":"Customer",
"Type":"int32"
},{
"Name":"Countries",
"Type":"int32"
}]
}
And whatever I have done so far, I have attached below:
package main
import (
"encoding/json"
"fmt"
"reflect"
)
func check(e error) {
if e != nil {
panic(e)
}
}
func getFields(t reflect.Type, prefix string) {
switch t.Kind() {
case reflect.Ptr:
getFields(t.Elem(), "")
case reflect.Struct:
buildRecursiveFunction(t)
}
}
func buildRecursiveFunction(t reflect.Type) map[string]interface{} {
var jsonArr []interface{}
temp := make(map[string]interface{})
for i := 0; i < t.NumField(); i++ {
sf := t.Field(i)
if sf.Name != "state" && sf.Name != "unknownFields" && sf.Name != "sizeCache" {
obj := make(map[string]interface{})
varName := sf.Name
varType := sf.Type.Kind()
obj["Name"] = varName
obj["Type"] = varType.String()
if varType.String() == "ptr" {
obj["Fields"] = buildRecursiveFunction(sf.Type.Elem())
} else {
}
jsonArr = append(jsonArr, obj)
}
}
jsonArrVal, _ := json.Marshal(jsonArr)
fmt.Println(string(jsonArrVal))
return temp
}
func main() {
getFields(reflect.TypeOf(&DeviceEnv{}), "")
// json.Marshal(AllAttributes)
}
Any help is appreciated.
I've hacked around your code and got it (kind of) working here, but it doesn't seem like it's structured in the best way. Better to return and build a recursive object than trying to print it (with a prefix?) as you go.
What about defining a TypeInfo struct and having a recursive function that populates that? I think that leads to a clear structure, and it allows the caller to JSON-marshal the result as they want to:
func main() {
info := MakeTypeInfo("DeviceEnvironment", DeviceEnv{})
b, err := json.MarshalIndent(info, "", " ")
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", b)
}
type TypeInfo struct {
Name string `json:"Name"`
Type string `json:"Type"`
Fields []TypeInfo `json:"Fields,omitempty"`
}
func MakeTypeInfo(name string, value interface{}) TypeInfo {
return makeTypeInfo(name, reflect.TypeOf(value))
}
func makeTypeInfo(name string, t reflect.Type) TypeInfo {
kind := t.Kind()
switch kind {
case reflect.Struct:
var fields []TypeInfo
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fields = append(fields, makeTypeInfo(field.Name, field.Type))
}
return TypeInfo{Name: name, Type: kind.String(), Fields: fields}
case reflect.Pointer:
return makeTypeInfo(name, t.Elem())
default:
return TypeInfo{Name: name, Type: kind.String()}
}
}
Note that I haven't done the field filtering (eg: "unknownFields") that you've shown in your code -- shouldn't be hard to add inside the reflect.Struct case though.
Full example in Go Playground.
I am writing a simple post api request. I am able to parse the JSON into golang structs upto the peername json object. I do not know the correct syntax to populate a golang slice of a struct by passing values through the JSON body of the api.
I am trying to parse JSON body sent through an api. This is the sample body request -
{
"type":"string",
"name":"string",
"organization":{
"orgID":"1",
"orgName":"string",
"peer":{
"peerID":"1",
"peerName":"string"
},
"attributes":[
["slide0001.html", "Looking Ahead"],
["slide0008.html", "Forecast"],
["slide0021.html", "Summary"]
]
}
} "peerName":"string"
},
"attributes":["name":"string":"value":true]
}
}
And this is my sample golang structs.
//Identity ...
type Identity struct {
Type string `json:"type,omitempty"`
Name string `json:"name,omitempty"`
Organization *Organization `json:"organization,omitempty"`
}
//Organization ....
type Organization struct {
OrgID string `json:"orgID,omitempty"`
OrgName string `json:"orgName,omitempty"`
Peer *Peer `json:"peer,omitempty"`
Attributes *Attributes `json:"attributes"`
}
//Peer ...
type Peer struct {
PeerID string `json:"peerID,omitempty"`
PeerName string `json:"peerName,omitempty"`
}
//Attributes ...
type Attributes []struct {
Name string `json:"name"`
Value bool `json:"value"`
}
Finally figured out the correct syntax. We have to pass an array of structs through JSON.
{
"type":"string",
"name":"string",
"organization":
{
"orgID":"1",
"orgName":"string",
"peer":
{
"peerID":"1",
"peerName":"string"
},
"attributes":
[
{"slide0001.html": "Looking Ahead"},
{"slide0008.html": "Forecast"},
{"slide0021.html": "Summary"}
]
}
}
you can do whatever you want in a UnmarshalJSON Function.
i made an example in playground. https://play.golang.org/p/WY6OCR8K3Co
you can get output: {A:[{Name:slide0001.html Value:Looking Ahead} {Name:slide0008.html Value:Forecast} {Name:slide0021.html Value:Summary}]}
var (
jso = []byte(`
{
"attributes":
[
{"slide0001.html": "Looking Ahead"},
{"slide0008.html": "Forecast"},
{"slide0021.html": "Summary"}
]
}`)
)
type B struct {
A As `json:"attributes"`
}
type As []A
type A struct {
Name string
Value string
}
func (as *As) UnmarshalJSON(data []byte) error {
var attr []interface{}
if err := json.Unmarshal(data, &attr); err != nil {
return err
}
if len(attr) > 0 {
newAs := make([]A, len(attr))
// i := 0
for i, val := range attr {
if kv, ok := val.(map[string]interface{}); ok && len(kv) > 0 {
for k, v := range kv {
a := A{
Name: k,
Value: v.(string),
}
newAs[i] = a
i++
break
}
}
}
*as = newAs
}
return nil
}
Json is -
{
"apiAddr":"abc",
"data":
[
{
"key":"uid1",
"name":"test",
"commandList":["dummy cmd"],
"frequency":"1",
"deviceList":["dev1"],
"lastUpdatedBy": "user",
"status":"Do something"
}
]
}
And the code to unmarshall is -
type Data struct {
APIAddr string `json:"apiAddr"`
Data []Template `json:"data"`
}
type Template struct {
Key string `json:"key"`
Name string `json:"name"`
CommandList []string `json:"commandList"`
Frequency string `json:"frequency"`
DeviceList []string `json:"deviceList"`
LastUpdatedBy string `json:"lastUpdatedBy"`
Status string `json:"status"`
}
raw, err := ioutil.ReadFile(*testFile)
if err != nil {
return
}
var testTemplates Data
err = json.Unmarshal(raw, &testTemplates)
if err != nil {
return
}
where testFile is the json file.
I am getting this error
json: cannot unmarshal array into Go value of type main.Data.
Looking at the existing questions in stackoverflow, looks like I am doing all right.Anyone?
Made a few modification and Unmarshaling worked just fine.
package main
import (
"encoding/json"
"fmt"
)
var raw = ` {
"apiAddr":"abc",
"data":
[
{
"key":"uid1",
"name":"test",
"commandList":["dummy cmd"],
"frequency":"1",
"deviceList":["dev1"],
"lastUpdatedBy": "user",
"status":"Do something"
}
]
}`
func main() {
var testTemplates Data
err := json.Unmarshal([]byte(raw), &testTemplates)
if err != nil {
return
}
fmt.Println("Hello, playground", testTemplates)
}
type Data struct {
APIAddr string `json:"apiAddr"`
Data []Template `json:"data"`
}
type Template struct {
Key string `json:"key"`
Name string `json:"name"`
CommandList []string `json:"commandList"`
Frequency string `json:"frequency"`
DeviceList []string `json:"deviceList"`
LastUpdatedBy string `json:"lastUpdatedBy"`
Status string `json:"status"`
}
You can run it in Playground as well: https://play.golang.org/p/TSmUnFYO97-
I have a third-party json api to work with in go.
It has some endpoints which return data as key-value.
For example here is json for statuses:
{
"result": {
"0": "done",
"1": "incomplete",
"2": "completed",
....
}
}
So as you see it is not an array it is an object.
Is it possible to marshal this kind of json to array of objects like
type Status struct {
Id int
Status string
}
without using additional struct like
type StatusReposne struct {
Result map[string]string `json:"result"`
}
and code to extract values?
As #mkopriva stated in the comment, it's not possible without some extra work. This code does provide means to Marshal/Unmarshal data to/from a slice of Statuss:
func main() {
sr := new(StatusReposne)
json.Unmarshal([]byte(input), sr)
fmt.Printf("%+v\n", sr)
js, _ := json.Marshal(sr)
fmt.Printf("%s\n", js)
}
type StatusReposne struct {
Result []Status `json:"result"`
}
type Status struct {
Id int
Status string
}
func (x *StatusReposne) MarshalJSON() ([]byte, error) {
var buffer struct {
Result map[string]string `json:"result"`
}
buffer.Result = make(map[string]string)
for _, v := range x.Result {
buffer.Result[strconv.Itoa(v.Id)] = v.Status
}
return json.Marshal(&buffer)
}
func (x *StatusReposne) UnmarshalJSON(b []byte) error {
var buffer struct {
Result map[string]string `json:"result"`
}
buffer.Result = make(map[string]string)
json.Unmarshal(b, &buffer)
for k, v := range buffer.Result {
k, _ := strconv.Atoi(k)
x.Result = append(x.Result, Status{Id: k, Status: v})
}
return nil
}
var input = `{
"result": {
"0": "done",
"1": "incomplete",
"2": "completed"
}
}`
type Struct struct {
Value string `json:"value"`
Value1 string `json:"value_one"`
Nest Nested `json:"nest"`
}
type Nested struct {
Something string `json:"something"`
}
I want to add elements which are not in the structs definitions without creating another struct type. For example
Struct.Extra1 = Nested{"yy"}
Struct.Nested.Extra2 = "zz"
Which will result
{
"Value": "xx",
"Value1": "xx",
"Extra1": {
"Something", "yy"
},
"Nest": {
"Something": "xx",
"Extra2": "zz"
}
}
SOLUTION1:
I thought of adding omitempty to achieve this but it makes the structs complex.
type Struct struct {
Value string
Value1 string
Nest Nested
Extra1 Nested `json:"omitempty"`
}
type Nested struct {
Something string
Extra2 string `json:"omitempty"`
}
SOLUTION2:
myextras := make(map[string]interface{})
// get Struct.Nested in map[string]interface{} format
myextras = Struct.Nest
myextras["Extra2"] = "zz"
// get Struct in map[string]interface{} format
struct["Nest"] = myextras
struct["Extra1"] = Nested{"yy"}
// solves the problem with lots of type casting but doesn't support json tag naming
Is there a better solution to add nested elements which are not represented in struct datatype with json-tag support and could be used to output to user.
If someone is not happy with the solution provided:
Try tidwall/sjson. It provides functions for quick JSON editing without having to define any structure. It saved me a bunch of time yesterday :D
Example usage:
value, _ := sjson.Set(`{"name":{"last":"Anderson"}}`, "name.last", "Smith")
println(value)
// Output:
// {"name":{"last":"Smith"}}
Based on this answer: Can I use MarshalJSON to add arbitrary fields to a json encoding in golang?
You could do something like (demo: http://play.golang.org/p/dDiTwxhoNn):
package main
import (
"encoding/json"
"fmt"
"log"
)
type Book struct {
Title string
Author string
// extra is used for additional dynamic element marshalling
extra func() interface{}
}
type FakeBook Book
func (b *Book) SetExtra(fn func() interface{}) {
b.extra = fn
}
func (b *Book) MarshalJSON() ([]byte, error) {
if b.extra == nil {
b.extra = func() interface{} { return *b }
}
return json.Marshal(b.extra())
}
func main() {
ms := &Book{
Title: "Catch-22",
Author: "Joseph Heller",
}
ms.SetExtra(func() interface{} {
return struct {
FakeBook
Extra1 struct {
Something string `json:"something"`
} `json:"extra1"`
}{
FakeBook: FakeBook(*ms),
Extra1: struct {
Something string `json:"something"`
}{
Something: "yy",
},
}
})
out, err := json.MarshalIndent(ms, "", " ")
if err != nil {
log.Fatalln(err)
}
fmt.Println(string(out))
mb := &Book{
Title: "Vim-go",
Author: "Fatih Arslan",
}
mb.SetExtra(func() interface{} {
return struct {
FakeBook
Something string `json:"something"`
}{
FakeBook: FakeBook(*mb),
Something: "xx",
}
})
out, err = json.MarshalIndent(mb, "", " ")
if err != nil {
log.Fatalln(err)
}
fmt.Println(string(out))
mc := &Book{
Title: "Another-Title",
Author: "Fatih Arslan",
}
out, err = json.MarshalIndent(mc, "", " ")
if err != nil {
log.Fatalln(err)
}
fmt.Println(string(out))
}
yes. there is a type json.Raw which not a struct but []byte. you can manage it out of struct, in any marshal/unmarshal way.
UPDATE
use any way to edit json string on the fly
package main
import (
"fmt"
"encoding/json"
"strings"
)
type Struct struct {
Value string `json:"value"`
Value1 string `json:"value_one"`
Nest json.RawMessage`json:"nest"`
}
func main() {
s := Struct{Value1: "struct string"}
buf, _ := json.Marshal(s)
fmt.Println(string(buf))
s2 := strings.ReplaceAll(string(buf), "null", `{"extra2":{"anykey":3.1415926535}}`)
fmt.Println(s2)
}
https://play.golang.org/p/lOCxJBs5iRJ
The map approach is the only sane way to do it, everything else (like json.RawMessage fields would require an extra marshalling step anyway.
if you don't want install extra package, you can change or add new values with this code :
package main
import "fmt"
import "strings"
import "encoding/json"
func main() {
s := []byte(`{ "level1a":"aaa", "level1b":{"level2a":"bbb", "level2b":{"level3":"aaa"} } }`)
var j interface{}
json.Unmarshal(s, &j)
SetValueInJSON(j, "level1a", "new value 1a")
SetValueInJSON(j, "level1b.level2a", "new value 2a")
SetValueInJSON(j, "level1b.level2b.level3", "new value 3")
SetValueInJSON(j, "level1b.level2c", "new key")
s,_ = json.Marshal(j)
fmt.Println(string(s))
// result: {"level1a":"new value 1a","level1b":{"level2a":"new value 2a","level2b":{"level3":"new value 3"},"level2c":"new key"}}
}
func SetValueInJSON(iface interface{}, path string, value interface{}) interface{} {
m := iface.(map[string]interface{})
split := strings.Split(path, ".")
for k, v := range m {
if strings.EqualFold(k, split[0]) {
if len(split) == 1 {
m[k] = value
return m
}
switch v.(type) {
case map[string]interface{}:
return SetValueInJSON(v, strings.Join(split[1:], "."), value)
default:
return m
}
}
}
// path not found -> create
if len(split) == 1 {
m[split[0]] = value
} else {
newMap := make(map[string]interface{})
newMap[split[len(split)-1]] = value
for i := len(split) - 2; i > 0; i-- {
mTmp := make(map[string]interface{})
mTmp[split[i]] = newMap
newMap = mTmp
}
m[split[0]] = newMap
}
return m
}