Golang jsonable pointers to different structs in slice - json

My golang program have this structure of structs:
type JSONDoc struct {
Count int `json:"count"`
Objects []uintptr `json:"objects"`
}
type ObjectA struct {
FieldA string
}
type ObjectB struct {
FieldB string
}
I don't know what object types can be in JSONDoc.Objects, i'm need to store multiple structs in json array. Reflect returns pointers to structs, i'm appending them to struct, but encoding/json package in result json replace pointer with integer address.
Also unsafe.Pointer cannot be parsed by encoding/json too.
Just want result json to look as
{
"count":2,
"objects":
[
{"FieldA":"..."},
{"FieldB":"..."}
]
}
How can i store pointers to structs that will be correctly encoded to json?

You could use interface{}, for example :
type JSONDoc struct {
Count int `json:"count"`
Objects []interface{} `json:"objects"`
}
func main() {
doc := JSONDoc{Count: 2}
doc.Objects = append(doc.Objects, &ObjectA{"A"}, &ObjectB{"B"})
b, err := json.MarshalIndent(&doc, "", "\t")
fmt.Println(string(b), err)
}
playground

Related

How to use FieldByName to extract nested struct value

Here is the struct:
type UP struct {
Rxinfo []struct {
Gatewayid string `json:"gatewayID"`
Uplinkid string `json:"uplinkID"`
Name string `json:"name"`
Time time.Time `json:"time"`
Rssi int `json:"rssi"`
Lorasnr float64 `json:"loRaSNR"`
Location struct {
Latitude int `json:"latitude"`
Longitude int `json:"longitude"`
Altitude int `json:"altitude"`
} `json:"location"`
} `json:"rxInfo"`
Adr bool `json:"adr"`
Fcnt int `json:"fCnt"`
Data string `json:"data"`
}
When I code like this:
up := UP{}
if err := json.Unmarshal(msg.Payload(), &up); err != nil {
fmt.Printf("Message could not be parsed (%s): %s", msg.Payload(), err)
}
val := reflect.ValueOf(up).FieldByName("Name")
fmt.Printf("%v",val)
It returns <invalid reflect.Value>.
The reason why the logs return an <invalid reflect.Value> error is because the field Name is located inside a slice of structs. If you want to get the value of a field from a slice of struct, you have to specify which index of the slice you want to get value from.
For example, your payload looks like this:
{
"rx_info":[
{
"gateway_id":"1",
"up_link_id":"2",
"name":"sunstrider",
"time":"2021-05-25T19:37:00Z",
"rssi":-21,
"lo_ra_snr":0.342,
"location":{
"latitiude":413,
"longitude":124,
"altitude":41
}
},
{
"gateway_id":"2",
"up_link_id":"4",
"name":"sunstrider 2",
"time":"2021-06-25T19:37:00Z",
"rssi":-41,
"lo_ra_snr":0.562,
"location":{
"latitiude":213,
"longitude":321,
"altitude":443
}
}
],
"address":true,
"fcnt":53,
"data":"this is the data"
}
Notice how the field Name is inside an object that's inside an array. If your payload looks like this, then your code should look like this:
package main
import (
"encoding/json"
"fmt"
"reflect"
"time"
)
type UP struct {
RxInfo []RxInfo `json:"rx_info"`
Address bool `json:"address"`
FCNT int `json:"fcnt"`
Data string `json:"data"`
}
type RxInfo struct {
GatewayID string `json:"gateway_id"`
UplinkID string `json:"uplink_id"`
Name string `json:"name"`
Time time.Time `json:"time"`
RSSI int `json:"rssi"`
LoRaSNR float64 `json:"lo_ra_snr"`
Location Location `json:"location"`
}
type Location struct {
Latitude int `json:"latitude"`
Longitude int `json:"longitude"`
Altitude int `json:"altitude"`
}
func main() {
up := UP{}
if err := json.Unmarshal([]byte(payload), &up); err != nil {
fmt.Printf("Message could not be parsed (%s): %s", payload, err)
}
// because the "Name" field is inside an array, we have to
// bind the payload array to an array of a predefined struct
valSlice := reflect.ValueOf(up).FieldByName("RxInfo").Interface().([]RxInfo)
// getting the value from the zeroth index
fmt.Println(valSlice[0].Name)
// getting the value from the first index
fmt.Println(valSlice[1].Name)
// since your goal is to get the value of a field, I suggest you
// get the value using the structure of the struct using
// "dot" operator rather than of using the FieldByName()
valString := up.RxInfo[0].Name
fmt.Println(valString)
}
Also, a couple of tips to keep your code clean:
You should always access the value of the payload using the structure of your struct. Only use reflect.ValueOf(variable).FieldByName("name_of_field") when you don't know the complete structure of your payload.
If you have a deeply nested struct, split them up into multiple tiny structs (like my example above) to make it easier to read and maintain.
Standardize your naming convention. This applies when you're naming variables, structs, struct fields, struct tags, etc. I personally use camel-case for field names and snake-case for json struct tags.

how to decode a multi/single value in Json

I want to decode a request JSON that the value of a field could be a single string or array (list).
I know how to parse if separately. but I'm looking for a way to have a dynamic decoder to parse both of them.
The value field in the JSON is the case I'm talking about
Sample for single-value:
{
"filter":{
"op":"IN",
"field":"name",
"value": "testDuplicate"
}
}
Sample for multi-value:
{
"filter":{
"op":"IN",
"field":"name",
"value":["testDuplicate","tt"]
}
}
Structs for single value:
type DocumentTypeSearchRequest struct {
Filter DocTypeFilter `json:"filter"`
}
type DocTypeFilter struct {
Op string `json:"op"`
Field string `json:"field"`
Value string `json:"value"`
}
Structs for multi-value:
type DocumentTypeSearchRequest struct {
Filter DocTypeFilter `json:"filter"`
}
type DocTypeFilter struct {
Op string `json:"op"`
Field string `json:"field"`
Value []string `json:"value"`
}
One solution is to try to decode with one of them if failed use another one, but I'm not sure if that is the proper way to handle this problem.
You need a custom unmarshaler. I've talked about a similar situation in this video:
type Value []string
func (v *Value) UnmarshalJSON(p []byte) error {
if p[0] == '[' { // First char is '[', so it's a JSON array
s := make([]string, 0)
err := json.Unmarshal(p, &s)
*v = Value(s)
return err
}
// else it's a simple string
*v = make(Value, 1)
return json.Unmarshal(p, &(*v)[0])
}
Playground link
With this definition of Value, your struct would become something like:
type DocTypeFilter struct {
Op string `json:"op"`
Field string `json:"field"`
Value Value `json:"value"`
}
and now your Value field will always be a slice, but it may contain only a single value when your JSON input is a string, and not an array.

Why would adding a func in an interface cause json.Unmarshal to fail?

Why does this fail with the error :
json: cannot unmarshal object into Go struct field Person.spouse of type main.Spouse
type Spouse interface {
Name() // Adding this causes an error
}
type Address interface {
}
type Person struct {
Name string `json:"name"`
A *Address `json:"address"`
S *Spouse `json:"spouse"`
}
func main() {
b := []byte(`{
"name":"sarah",
"address":{
"street":"101 main"
},
"spouse":{
"name":"joe"
}
}
`)
p := &Person{}
if err := json.Unmarshal(b, p); err != nil {
fmt.Printf("%s", err)
}
}
Looking at the docs, I don't see why adding a function in an interface would cause an error. I was expecting json.Unmarshal to simply ignore the Name() function since it's not part of the list processed by json.Unmarshal.
Here is the code in go playground.
go version go1.10 darwin/amd64
From the fine manual:
func Unmarshal
[...]
To unmarshal JSON into an interface value, Unmarshal stores one of these in the interface value:
bool, for JSON booleans
float64, for JSON numbers
string, for JSON strings
[]interface{}, for JSON arrays
map[string]interface{}, for JSON objects
nil for JSON null
You're trying to unmarshal a JSON object into an interface so the value behind that interface is going to be a map[string]interface{} to represent the key/value pairs. But map[string]interface{} doesn't have a Name() method so it doesn't implement your Spouse interface.
If we simplify your example a little and get rid of the Name() method we can see what's going on:
type Spouse interface {
}
type Person struct {
S *Spouse `json:"spouse"`
}
func main() {
b := []byte(`{"spouse":{ "name":"joe" } }`)
p := &Person{}
if err := json.Unmarshal(b, p); err != nil {
fmt.Printf("%s", err)
}
spouse := (*p.S).(map[string]interface{})
fmt.Printf("%+v\n", spouse)
}
spouse is a map[string]interface{} as documented.
The problem is that you are trying to map the value of spouse in the json with a particular data type that doesn't match. As reported in this article, json uses empty interface to map generic data types to the right one used in the file.
The json package uses map[string]interface{} and []interface{} values
to store arbitrary JSON objects and arrays; it will happily unmarshal
any valid JSON blob into a plain interface{} value. The default
concrete Go types are:
bool for JSON booleans,
float64 for JSON numbers,
string for JSON strings, and
nil for JSON null.
If you want to see it from another perspective, it means that you can't use anything other than these data types to map data in JSON. And your not empty interface isn't a valid type.

Golang json decoding fails on Array

I have an object like:
a = [{
"name": "rdj",
"place": "meh",
"meh" : ["bow", "blah"]
}]
I defined a struct like:
type first struct {
A []one
}
type one struct {
Place string `json: "place"`
Name string `json: "name"`
}
when I use the same in code like:
func main() {
res, _ := http.Get("http://127.0.0.1:8080/sample/")
defer res.Body.Close()
var some first
rd := json.NewDecoder(res.Body)
err := rd.Decode(&some)
errorme(err)
fmt.Printf("%+v\n", some)
}
I get the below error:
panic: json: cannot unmarshal array into Go value of type main.first
My understanding is:
type first defines the array and within that array is data structure defined in type one.
The JSON is an array of objects. Use these types:
type one struct { // Use struct for JSON object
Place string `json: "place"`
Name string `json: "name"`
}
...
var some []one // Use slice for JSON array
rd := json.NewDecoder(res.Body)
err := rd.Decode(&some)
The the types in the question match this JSON structure:
{"A": [{
"name": "rdj",
"place": "meh",
"meh" : ["bow", "blah"]
}]}
#rickydj,
If you need to have "first" as a separate type:
type first []one
If you do not care about having "first" as a separate type, just cut down to
var some []one
as #Mellow Marmot suggested above.

Accessing Nested Map of Type map[string]interface{} in Golang

So I'm trying to parse a JSON response. It can be multiple levels deep. This is what I did:
var result map[string]interface{}
json.Unmarshal(apiResponse, &result)
Firstly, is this the right way to do it?
Lets say the response was as follows:
{
"args": {
"foo": "bar"
}
}
To access key foo, I saw a playground doing this:
result["args"].(map[string]interface{})["foo"]
Here, what is the .() notation? Is this correct?
The notation x.(T) is called a Type Assertion.
For an expression x of interface type and a type T, the primary expression x.(T) asserts that x is not nil and that the value stored in x is of type T.
Your example:
result["args"].(map[string]interface{})["foo"]
It means that the value of your results map associated with key "args" is of type map[string]interface{} (another map with string keys and any values). And you want to access the element of that map associated with the key "foo".
If you know noting about the input JSON format, then yes, you have to use a generic map[string]interface{} type to process it. If you know the exact structure of the input JSON, you can create a struct to match the expected fields, and doing so you can unmarshal a JSON text into a value of your custom struct type, for example:
type Point struct {
Name string
X, Y int
}
func main() {
in := `{"Name":"center","X":2,"Y":3}`
pt := Point{}
json.Unmarshal([]byte(in), &pt)
fmt.Printf("Result: %+v", pt)
}
Output:
Result: {Name:center X:2 Y:3}
Try it on the Go Playground.
Modeling your input
Your current JSON input could be modelled with this type:
type Data struct {
Args struct {
Foo string
}
}
And accessing Foo (try it on the Go Playground):
d := Data{}
json.Unmarshal([]byte(in), &d)
fmt.Println("Foo:", d.Args.Foo)
struct is the best option, but if you insist, you can add a type declaration for a map, then you can add methods to help
with the type assertions:
package main
import "encoding/json"
type dict map[string]interface{}
func (d dict) d(k string) dict {
return d[k].(map[string]interface{})
}
func (d dict) s(k string) string {
return d[k].(string)
}
func main() {
apiResponse := []byte(`{"args": {"foo": "bar"}}`)
var result dict
json.Unmarshal(apiResponse, &result)
foo := result.d("args").s("foo")
println(foo == "bar")
}
https://golang.org/ref/spec#Type_declarations