JSON decode with flexible type - go

I need to specify a type for decoding JSON data in a flexible manner, meaning the type needs to be specified at runtime.
Consider this snippet: http://play.golang.org/p/F-Jy4ufMPz
s := `{"b":[{"x":9},{"x":4}]}`
var a struct {
B []interface{}
}
err := json.Unmarshal([]byte(s), &a)
if err != nil {
panic(err)
}
fmt.Println(a)
Which will produce {[map[x:9] map[x:4]]}. I want to decode to an array of a specific (struct) type instead of []interface{}, without specifying it at compile time.
Is that somehow possible without creating the array up front? (the number of returned items is unknown)
The only way I can think of right now is to encode the returned maps again later, and decode them to the specified type, which would create unnecessary processing overhead.

If not specifying it at compile time, you still need to specify it somewhere.
If specified before the retrieval of the Json data, you can simply do a switch case, Unmarshalling it to your desired object.
If specified within the Json data, you can marshal the "flexible" part into a json.RawMessage to process it after you've decided what type of struct is suitable:
package main
import (
"encoding/json"
"fmt"
)
var s = `{"type":"structx", "data":{"x":9,"xstring":"This is structX"}}`
type JsonStruct struct {
Type string
Data json.RawMessage
}
type StructX struct {
X float64
Xstring string
}
type StructY struct {
Y bool
}
func main() {
var a *JsonStruct
err := json.Unmarshal([]byte(s), &a)
if err != nil {
panic(err)
}
switch a.Type {
case "structx":
// We Unmashal the RawMessage part into a StructX
var s *StructX
json.Unmarshal([]byte(a.Data), &s)
if err != nil {
panic(err)
}
fmt.Println(s)
case "structy":
// Do the same but for structY
}
}
Playground

Related

Returning a pointer to a struct from a function in Go

I have two public structs that contain different data, and a private intermediate struct containing either of the two public structs. I also have a function that unmarshalls the intermediate struct, determines which public struct it contains, and returns one of the two public structs.
The problem I'm facing is the return value of the last function. At it's simplest I thought I could return *struct{} but I keep getting a type mismatch in my IDE.
I apologize for posting more code than is probably necessary, but I'm trying to make it as close as possible to the code I'm working on.
package main
import (
"encoding/json"
"errors"
)
// These vars are some errors I'll use in the functions later on
var (
errInvalidBase64 = errors.New("invalid base64")
errInvalidStructType = errors.New("invalid struct type")
)
// Struct1 public struct
type Struct1 struct {
FName string `json:"first-name"`
LName string `json:"last-name"`
}
// Struct2 public struct
type Struct2 struct {
Date string `json:"date"`
Items []int `json:"items"`
}
// intermediateStruct private struct
// The Type field indicates which kind of struct Data contains (Struct1 or Struct2)
// The Data field contains either Struct1 or Struct2 which was previously marshalled into JSON
type intermediateStruct struct {
Type structType
Data []byte
}
// The following type and const are my understanding of an enum in Go
// structType is a private type for the type of struct intermediateStruct contains
type structType int
// These public constants are just to keep my hands out of providing values for the different struct types
const (
StructType1 structType = iota
StructType2
)
// unmarshalStruct1 unmarshalls JSON []byte into a new Struct1 and returns a pointer to that struct
func unmarshalStruct1(b []bytes) (*Struct1, error) {
newStruct1 := new(Struct1)
err := json.Unmarshal(b, newStruct1)
if err != nil {
return nil, errInvalidBase64
}
return newStruct1, nil
}
// unmarshalStruct2 unmarshalls JSON []byte into a new Struct2 and returns a pointer to that struct
func unmarshalStruct2(b []bytes) (*Struct2, error) {
newStruct2 := new(Struct2)
err := json.Unmarshal(b, newStruct2)
if err != nil {
return nil, errInvalidBase64
}
return newStruct2, nil
}
// receiveStruct accepts *intermediateStruct who's Data field contains either Struct1 or Struct2
// This function needs to return either *Struct1 or *Struct2 and an error
func receiveStruct(iStruct *intermediateStruct) (*struct{}, error) {
switch iStruct.Type {
case StructType1:
struct1, err := unmarshalStruct1(iStruct.Data)
if err != nil {
return nil, err
}
// The following line is where I'm getting the type mismatch
return struct1, nil
case StructType2:
struct2, err := unmarshalStruct2(iStruct.Data)
if err != nil {
return nil, err
}
// The following line is another type mismatch
return struct2, nil
default:
return nil, errInvalidStructType
}
}
I know there's a way to do what I'm trying to achieve - I just lack the experience/understanding to get there.
Thanks for any and all input!
Your unmarshallStruct function returns a pointer to type Struct1 or Struct2 (depending on which version of the function gets called). And therefore the variables struct1 and struct2 are pointers to types Struct1 and Struct2 respectively. Neither is a pointer to type struct (which is not a real Go type anyways I must add). A struct is a keyword which helps to declare types containing fields/attributes.
Depending on the use-cases you have in mind for the rest of your code, can instead try any of the below:
As mkopriva suggested, return a interface{} object, but you'd need to use type assertion to actually make sure of the object
Define an interface which both Struct1 and Struct2 implement, and return a pointer to this
Make separate functions which work with either Struct1 or Struct2. This is not necessarily as bad as it sounds as Go lets you pass functions in the same way you'd pass types (see example of the Less() function in sort package).

What is the equivalence of Type Name Handling with encoding/json in Go?

Short Description in Prose
I have a situation where I want to unmarshal JSON data into an array of structs (either Foo or Bar and many more) that all implement a common interface MyInterface. Also all of the eligble struct types that implement the interface have a common field which I named Discrimininator in the example below.
The Discriminator¹ allows to bi-uniquely find the correct struct type for each value of Discriminator.
The Problem and Error Message
But during unmarshling the code does not "know" which is the correct "target" type. The unmarshaling fails.
cannot unmarshal object into Go value of type main.MyInterface
MWE in
https://play.golang.org/p/Dw1hSgUezLH
package main
import (
"encoding/json"
"fmt"
)
type MyInterface interface {
// some other business logic methods go here!
GetDiscriminator() string // GetDiscriminator returns something like a key that is unique per struct type implementing the interface
}
type BaseStruct struct {
Discriminator string // will always be "Foo" for all Foos, will always be "Bar" for all Bars
}
type Foo struct {
BaseStruct
// actual fields of the struct don't matter. it's just important that they're different from Bar
FooField string
}
func (foo *Foo) GetDiscriminator() string {
return foo.Discriminator
}
type Bar struct {
BaseStruct
// actual fields of the struct don't matter. it's just important that they're different from Foo
BarField int
}
func (bar *Bar) GetDiscriminator() string {
return bar.Discriminator
}
// Foo and Bar both implement the interface.
// Foo and Bars are always distinguishible if we check the value of Discriminator
func main() {
list := []MyInterface{
&Bar{
BaseStruct: BaseStruct{Discriminator: "Bar"},
BarField: 42,
},
&Foo{
BaseStruct: BaseStruct{Discriminator: "Foo"},
FooField: "hello",
},
}
jsonBytes, _ := json.Marshal(list)
jsonString := string(jsonBytes)
fmt.Println(jsonString)
// [{"Discriminator":"Bar","BarField":42},{"Discriminator":"Foo","FooField":"hello"}]
var unmarshaledList []MyInterface
err := json.Unmarshal(jsonBytes, &unmarshaledList)
if err != nil {
// Unmarshaling failed: json: cannot unmarshal object into Go value of type main.MyInterface
fmt.Printf("Unmarshaling failed: %v", err)
}
}
In other languages
TypeNameHandling as known from .NET
In Newtonsoft, a popular .NET JSON Framework, this is solved by a something called "TypeNameHandling" or can be solved with a custom JsonConverter . The framework will add something like a magic "$type" key on root level to the serialized/marshaled JSON which is then used to determine the original type on deserialization/unmarshaling.
Polymorphism in ORMs
¹A similar situation occurs under the term "polymorphism" in ORMs when instances of multiple types having the same base are saved in the same table. One typically introduces a discriminator column, hence the name in above example.
You can implement a custom json.Unmarshaler. For that you'll need to use a named slice type instead of the unnamed []MyInterface.
Within the custom unmarshaler implementation you can unmarshal the JSON array into a slice where each element of the slice is a json.RawMessage representing the corresponding JSON object. After that you can iterate over the slice of raw messages. In the loop unmarshal from each raw message only the Discriminator field, then use the Discriminator field's value to determine what the correct type is into which the full raw message can be unmarshaled, finally unmarshal the full message and add the result to the receiver.
type MyInterfaceSlice []MyInterface
func (s *MyInterfaceSlice) UnmarshalJSON(data []byte) error {
array := []json.RawMessage{}
if err := json.Unmarshal(data, &array); err != nil {
return err
}
*s = make(MyInterfaceSlice, len(array))
for i := range array {
base := BaseStruct{}
data := []byte(array[i])
if err := json.Unmarshal(data, &base); err != nil {
return err
}
var elem MyInterface
switch base.Discriminator {
case "Foo":
elem = new(Foo)
case "Bar":
elem = new(Bar)
}
if elem == nil {
panic("whoops")
}
if err := json.Unmarshal(data, elem); err != nil {
return err
}
(*s)[i] = elem
}
return nil
}
https://play.golang.org/p/mXiZrF392aV

How to convert string to struct array

I have a json array which is converted into a string. Now I want to map the string to a struct array so that I can modify the string json. Below is my code base
type ProcessdetailsEntity struct {
Source []int64 `json:"source"`
Node string `json:"node"`
Targets []int64 `json:"targets"`
Issave bool `json:"isSave"`
Instate []int64 `json:"inState"`
OutState []int64 `json:"outState"`
}
func main() {
var stringJson = "[{\"source\":[-1],\"node\":\"1_1628008588902\",\"targets\":[],\"isSave\":true,\"inState\":[1],\"outState\":[2]},{\"source\":[\"1_1628008588902\",\"5_1628008613446\"],\"node\":\"2_1628008595757\",\"targets\":[],\"isSave\":true,\"inState\":[2,5],\"outState\":[3,6]}]"
in := []byte(stringJson)
detailsEntity := []ProcessdetailsEntity{}
err := json.Unmarshal(in, &detailsEntity)
if err != nil {
log.Print(err)
}
}
Now when I run this code base I got the error:
json: cannot unmarshal string into Go struct field ProcessdetailsEntity.source of type int64
How to properly map the string to struct so that I can modify the inState and outState value of the json ?
The error you get is already pretty much on the nose:
cannot unmarshal string into Go struct field ProcessdetailsEntity.source of type int64
That tells you that (at least one) of your source fields appears to have the wrong type: a string instead of something that can be represented by a int64.
So let's check your source fields in your stringJson:
"source":[-1]
"source":["1_1628008588902","5_1628008613446"]
As you can see the second source is an array of string. Hence the error.
To solve this you need to make sure that the source is an array of int. Unfortunately, 1_1628008588902 and 5_1628008613446 are not valid integers in Go.
I slightly modified your JSON and fixed your code an then it works:
package main
import (
"encoding/json"
"log"
)
type ProcessdetailsEntity struct {
Source []int64 `json:"source"`
Node string `json:"node"`
Targets []int64 `json:"targets"`
Issave bool `json:"isSave"`
Instate []int64 `json:"inState"`
OutState []int64 `json:"outState"`
}
func main() {
var stringJson = `[
{
"source":[-1],
"node":"1_1628008588902",
"targets":[],
"isSave":true,
"inState":[1],
"outState":[2]
},
{
"source":[11628008588902,51628008613446],
"node":"2_1628008595757",
"targets":[],
"isSave":true,
"inState":[2,5],
"outState":[3,6]
}
]`
in := []byte(stringJson)
detailsEntity := []ProcessdetailsEntity{}
err := json.Unmarshal(in, &detailsEntity)
if err != nil {
log.Print(err)
}
}
See: https://play.golang.org/p/kcrkfRliWJ5

Golang - create an object of the same type as passed

I'm trying to build a generic function which will parse input (in JSON) into a specified structure. The structure may vary at run-time, based on parameters which are passed to the function. I'm currently trying to achieve this by passing an object of the right type and using reflect.New() to create a new output object of the same type.
I'm then parsing the JSON into this object, and scanning the fields.
If I create the object and specify the type in code, everything works. If I pass an object and try to create a replica, I get an "invalid indirect" error a few steps down (see code).
import (
"fmt"
"reflect"
"encoding/json"
"strings"
)
type Test struct {
FirstName *string `json:"FirstName"`
LastName *string `json:"LastName"`
}
func genericParser(incomingData *strings.Reader, inputStructure interface{}) (interface{}, error) {
//******* Use the line below and things work *******
//parsedInput := new(Test)
//******* Use vvv the line below and things don't work *******
parsedInput := reflect.New(reflect.TypeOf(inputStructure))
decoder := json.NewDecoder(incomingData)
err := decoder.Decode(&parsedInput)
if err != nil {
//parsing error
return nil, err
}
//******* This is the line that generates the error "invalid indirect of parsedInput (type reflect.Value)" *******
contentValues := reflect.ValueOf(*parsedInput)
for i := 0; i < contentValues.NumField(); i++ {
//do stuff with each field
fmt.Printf("Field name was: %s\n", reflect.TypeOf(parsedInput).Elem().Field(i).Name)
}
return parsedInput, nil
}
func main() {
inputData := strings.NewReader("{\"FirstName\":\"John\", \"LastName\":\"Smith\"}")
exampleObject := new(Test)
processedData, err := genericParser(inputData, exampleObject)
if err != nil {
fmt.Println("Parsing error")
} else {
fmt.Printf("Success: %v", processedData)
}
}
If I can't create a replica of the object, then a way of updating / returning the one supplied would be feasible. The key thing is that this function must be completely agnostic to the different structures available.
reflect.New isn't a direct analog to new, as it can't return a specific type, it only can return a reflect.Value. This means that you are attempting to unmarshal into a *reflect.Value, which obviously isn't going to work (even if it did, your code would have passed in **Type, which isn't what you want either).
Use parsedInput.Interface() to get the underlying value after creating the new value to unmarshal into. You then don't need to reflect on the same value a second time, as that would be a reflect.Value of a reflect.Value, which again isn't going to do anything useful.
Finally, you need to use parsedInput.Interface() before you return, otherwise you are returning the reflect.Value rather than the value of the input type.
For example:
func genericParser(incomingData io.Reader, inputStructure interface{}) (interface{}, error) {
parsedInput := reflect.New(reflect.TypeOf(inputStructure).Elem())
decoder := json.NewDecoder(incomingData)
err := decoder.Decode(parsedInput.Interface())
if err != nil {
return nil, err
}
for i := 0; i < parsedInput.Elem().NumField(); i++ {
fmt.Printf("Field name was: %s\n", parsedInput.Type().Elem().Field(i).Name)
}
return parsedInput.Interface(), nil
}
https://play.golang.org/p/CzDrj6sgQNt

how to access deeply nested json keys and values

I'm writing a websocket client in Go. I'm receiving the following JSON from the server:
{"args":[{"time":"2013-05-21 16:57:17"}],"name":"send:time"}
I'm trying to access the time parameter, but just can't grasp how to reach deep into an interface type:
package main;
import "encoding/json"
import "log"
func main() {
msg := `{"args":[{"time":"2013-05-21 16:56:16", "tzs":[{"name":"GMT"}]}],"name":"send:time"}`
u := map[string]interface{}{}
err := json.Unmarshal([]byte(msg), &u)
if err != nil {
panic(err)
}
args := u["args"]
log.Println( args[0]["time"] ) // invalid notation...
}
Which obviously errors, since the notation is not right:
invalid operation: args[0] (index of type interface {})
I just can't find a way to dig into the map to grab deeply nested keys and values.
Once I can get over grabbing dynamic values, I'd like to declare these messages. How would I write a type struct to represent such complex data structs?
You may like to consider the package github.com/bitly/go-simplejson
See the doc: http://godoc.org/github.com/bitly/go-simplejson
Example:
time, err := json.Get("args").GetIndex(0).String("time")
if err != nil {
panic(err)
}
log.Println(time)
The interface{} part of the map[string]interface{} you decode into will match the type of that field. So in this case:
args.([]interface{})[0].(map[string]interface{})["time"].(string)
should return "2013-05-21 16:56:16"
However, if you know the structure of the JSON, you should try defining a struct that matches that structure and unmarshal into that. Ex:
type Time struct {
Time time.Time `json:"time"`
Timezone []TZStruct `json:"tzs"` // obv. you need to define TZStruct as well
Name string `json:"name"`
}
type TimeResponse struct {
Args []Time `json:"args"`
}
var t TimeResponse
json.Unmarshal(msg, &t)
That may not be perfect, but should give you the idea
I'm extremely new to Golang coming from Python, and have always struggled with encode/decoding json. I found gjson at https://github.com/tidwall/gjson, and it helped me immensely:
package main
import "github.com/tidwall/gjson"
func main() {
msg := (`{"args":[{"time":"2013-05-21 16:56:16", "tzs":[{"name":"GMT"}]}],"name":"send:time"}`)
value := gjson.Get(msg, "args.#.time")
println(value.String())
}
-----------------------
["2013-05-21 16:56:16"]
Additionally, I noticed the comment of how to convert into Struct
package main
import (
"encoding/json"
"fmt"
)
type msgFormat struct {
Time string `json:"time"`
Tzs msgFormatTzs `json:"tzs"`
Name string `json:"name"`
}
type msgFormatTzs struct {
TzsName string `json:"name"`
}
func main() {
msg := (`{"args":[{"time":"2013-05-21 16:56:16", "tzs":[{"name":"GMT"}]}],"name":"send:time"}`)
r, err := json.Marshal(msgFormatTzs{msg})
if err != nil {
panic(err)
}
fmt.Printf("%v", r)
}
Try on Go playground

Resources