I the following issue: i receive information in yaml form, it can come in 2 configurations-
asingle character:
Character:
Name: Stackoverflow
Type: Awesome
Or in a slice of multiple characters with an optional field of slice of friends:
Character:
- Name: Stackoverflow
Type: Awesome
Friends: [Ben, John]
- Name: Facebook
Type: Ok
Because I don't know which configuration will arrive I tried to unmarshal the yaml to interface{} and then cast it to one of the structs but it didn't worked out:
type CharacterConfig struct{
Name string `yaml:"Name"`
Type string `yaml:"Type"`
Friends []string `yaml:"Friends,omitempty"`
var d interface{}
err := yaml.Unmarshal([]byte(yamlData), &d)
if err != nil {
panic(err)
}
res, ok := d.(CharacterConfig)
if ok {
fmt.Println("CharacterConfig are ok")
}
res, ok := d.([]CharacterConfig)
if ok {
fmt.Println("[]CharacterConfig are ok")
}
But I don't receive any of the prints... when I debug is I can see the unmarshal into interface worked, but non of the castings.
I know I can just unmarshal straight into the structs, but I don't understand wht what i did didn't work.
Unmarshaling into interface{} will not magically guess you want the result in your custom CharacterConfig struct. Unmarshaling YAML into interface{} will use map[interface{}]interface{} to represent objects, and []interface{} to represent lists / arrays, recursively.
So the type assertions above yield ok = false because the values stored in them are maps or slices of interfaces{}. You may type assert those types, and the assertion will succeed, but you won't be any closer in getting those values as your structs.
Also note that your input YAML needs another wrapper layer:
type Character struct {
CC CharacterConfig `yaml:"Character"`
}
type CharacterList struct {
CCs []CharacterConfig `yaml:"Character"`
}
So an easy way would be to first try to unmarshal into a value of type Character, and if that succeeds, you're done. If not, try to unmarshal again, this time into CharacterList.
Here's an example code that does that:
func unmarshal(data []byte) {
var c Character
err := yaml.Unmarshal(data, &c)
if err == nil {
fmt.Printf("It's Character: %+v\n", c)
return
}
var cl CharacterList
err = yaml.Unmarshal(data, &cl)
if err != nil {
panic(err)
}
fmt.Printf("It's CharacterList: %+v\n", cl)
}
Testing it like this:
func main() {
unmarshal([]byte(src))
unmarshal([]byte(src2))
}
const src = `Character:
Name: Stackoverflow
Type: Awesome`
const src2 = `Character:
- Name: Stackoverflow
Type: Awesome
Friends: [Ben, John]
- Name: Facebook
Type: Ok`
Output will be (try it on the Go Playground):
It's Character: {CC:{Name:Stackoverflow Type:Awesome Friends:[]}}
It's CharacterList: {CCs:[{Name:Stackoverflow Type:Awesome Friends:[Ben John]} {Name:Facebook Type:Ok Friends:[]}]}
Similar to what you did but without interface:
type CharacterConfig struct{
Name string `yaml:"Name"`
Type string `yaml:"Type"`
Friends []string `yaml:"Friends,omitempty"`
}
var a CharacterConfig
var b []CharacterConfig
err := yaml.Unmarshal([]byte(yamlData), &a)
if err == nil {
// a is valid format
}
err = yaml.Unmarshal([]byte(yamlData), &b)
if err == nil {
// b is valid format
}
Related
I'm trying to unmarshal the following YAML data into Go structures.
The data is the in the following format:
fetchers:
- type: "aws"
config:
omega: "lul"
- type: "kubernetes"
config:
foo: "bar"
Based of the type field, I want to determine wether to unmarshal the config field into awsConfig or kubernetesConfig struct.
My current code looks like this (using "gopkg.in/yaml.v2"):
type kubernetesConfig struct {
foo string `yaml:"foo"`
}
type awsConfig struct {
omega string `yaml:"omega"`
}
var c struct {
Fetchers []struct {
Type string `yaml:"type"`
Config interface{} `yaml:"config"`
} `yaml:"fetchers"`
}
err := yaml.Unmarshal(data, &c)
if err != nil {
log.Fatal(err)
}
for _, val := range c.Fetchers {
switch val.Type {
case "kubernetes":
conf := val.Config.(kubernetesConfig)
fmt.Println(conf.foo)
case "aws":
conf := val.Config.(awsConfig)
fmt.Println(conf.omega)
default:
log.Fatalf("No matching type, was type %v", val.Type)
}
}
Code in playground: https://go.dev/play/p/klxOoHMCtnG
Currently it gets unmarshalled as map[interface {}]interface {}, which can't be converted to one of the structs above.
Error:
panic: interface conversion: interface {} is map[interface {}]interface {}, not main.awsConfig \
Do I have to implemented the Unmarshaler Interface of the YAML package with a custom UnmarshalYAML function to get this done?
Found the solution by implementing Unmarshaler Interface:
type Fetcher struct {
Type string `yaml:"type"`
Config interface{} `yaml:"config"`
}
// Interface compliance
var _ yaml.Unmarshaler = &Fetcher{}
func (f *Fetcher) UnmarshalYAML(unmarshal func(interface{}) error) error {
var t struct {
Type string `yaml:"type"`
}
err := unmarshal(&t)
if err != nil {
return err
}
f.Type = t.Type
switch t.Type {
case "kubernetes":
var c struct {
Config kubernetesConfig `yaml:"config"`
}
err := unmarshal(&c)
if err != nil {
return err
}
f.Config = c.Config
case "aws":
var c struct {
Config awsConfig `yaml:"config"`
}
err := unmarshal(&c)
if err != nil {
return err
}
f.Config = c.Config
}
return nil
}
This type of task - where you want to delay the unmarshaling - is very similar to how json.RawMessage works with examples like this.
The yaml package does not have a similar mechanism for RawMessage - but this technique can easily be replicated as outlined here:
type RawMessage struct {
unmarshal func(interface{}) error
}
func (msg *RawMessage) UnmarshalYAML(unmarshal func(interface{}) error) error {
msg.unmarshal = unmarshal
return nil
}
// call this method later - when we know what concrete type to use
func (msg *RawMessage) Unmarshal(v interace{}) error {
return msg.unmarshal(v)
}
So to leverage this in your case:
var fs struct {
Configs []struct {
Type string `yaml:"type"`
Config RawMessage `yaml:"config"` // delay unmarshaling
} `yaml:"fetchers"`
}
err = yaml.Unmarshal([]byte(data), &fs)
if err != nil {
return
}
and based on the config "Type" (aws or kubernetes), you can finally unmarshal the RawMessage into the correct concrete type:
aws := awsConfig{} // concrete type
err = c.Config.Unmarshal(&aws)
or:
k8s := kubernetesConfig{} // concrete type
err = c.Config.Unmarshal(&k8s)
Working example here: https://go.dev/play/p/wsykOXNWk3H
I am a little bit confused here and although I have searched a lot on this, something is clearly missing from my knowledge and I am asking your help.
I have created a Hyperledger Fabric Network and installed a chaincode in it. And I want to make a function that retrieves all the World State inputs about the Keys. I have done it already with the bytes.Buffer and it worked. But what I want to do is to do it with a struct.
So, I created the following struct that has only the key:
type WSKeys struct {
Key string `json: "key"`
Namespace string `json: "Namespace"`
}
And this is my code function:
func (s *SmartContract) getAllWsDataStruct(APIstub shim.ChaincodeStubInterface , args []string) sc.Response {
var keyArrayStr []WSKeys
resultsIterator, err := APIstub.GetQueryResult("{\"selector\":{\"_id\":{\"$ne\": null }} }")
if err != nil {
return shim.Error("Error occured when trying to fetch data: "+err.Error())
}
for resultsIterator.HasNext() {
// Get the next record
queryResponse, err := resultsIterator.Next()
if err != nil {
return shim.Error(err.Error())
}
fmt.Println(queryResponse)
var qry_key_json WSKeys
json.Unmarshal([]byte(queryResponse), &qry_key_json)
keyArray = append(keyArray, qry_key_json)
}
defer resultsIterator.Close()
all_bytes, _ := json.Marshal(keyArray)
fmt.Println(keyArray)
return shim.Success(all_bytes)
}
When executing the above I get the following error:
cannot convert queryResponse (type *queryresult.KV) to type []byte
I can get the results correctly if I, for example do this:
func (s *SmartContract) getAllWsDataStruct(APIstub shim.ChaincodeStubInterface , args []string) sc.Response {
var keyArray []string
resultsIterator, err := APIstub.GetQueryResult("{\"selector\":{\"_id\":{\"$ne\": null }} }")
if err != nil {
return shim.Error("Error occured when trying to fetch data: "+err.Error())
}
for resultsIterator.HasNext() {
// Get the next record
queryResponse, err := resultsIterator.Next()
if err != nil {
return shim.Error(err.Error())
}
fmt.Println(queryResponse)
keyArray = append(keyArray, queryResponse.Key)
}
defer resultsIterator.Close()
all_bytes, _ := json.Marshal(keyArray)
fmt.Println(keyArray)
return shim.Success(all_bytes)
}
But, why I get the above error when trying to add the queryResponse into a custom struct?
Do I need to add it to a struct that is only its type?
Please someone can explain what I am missing here?
The error statement is verbose enough to indicate, that your []byte conversion failed for the type queryResponse which, with a bit of lookup seems to be a struct type. In Go you cannot natively convert a struct instance to its constituent bytes without encoding using gob or other means.
Perhaps your intention was to use the Key record in the struct for un-marshalling
json.Unmarshal([]byte(queryResponse.Key), &qry_key_json)
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
I'm developing a tool that can be implemented to simplify the process of creating simple CRUD operations/endpoints. Since my endpoints don't know what kind of struct they'll be receiving, I've created an interface that users can implement, and return an empty object to be filled.
type ItemFactory interface {
GenerateEmptyItem() interface{}
}
And the users would implement something like:
type Test struct {
TestString string `json:"testString"`
TestInt int `json:"testInt"`
TestBool bool `json:"testBool"`
}
func (t Test) GenerateEmptyItem() interface{} {
return Test{}
}
When the Test object gets created, its type is "Test", even though the func returned an interface{}. However, as soon as I try to unmarshal some json of the same format into it, it strips it of its type, and becomes of type "map[string]interface {}".
item := h.ItemFactory.GenerateEmptyItem()
//Prints "Test"
fmt.Printf("%T\n", item)
fmt.Println(reflect.TypeOf(item))
err := ConvertRequestBodyIntoObject(r, &item)
if err != nil {...}
//Prints "map[string]interface {}"
fmt.Printf("%T\n", item)
Func that unmarshalls item:
func ConvertRequestBodyIntoObject(request *http.Request, object interface{}) error {
body, err := ioutil.ReadAll(request.Body)
if err != nil {
return err
}
// Unmarshal body into request object
err = json.Unmarshal(body, object)
if err != nil {
return err
}
return nil
}
Any suggestions as to why this happens, or how I can work around it?
Thanks
Your question lacks an example showing this behavior so I'm just guessing this is what is happening.
func Generate() interface{} {
return Test{}
}
func GeneratePointer() interface{} {
return &Test{}
}
func main() {
vi := Generate()
json.Unmarshal([]byte(`{}`), &vi)
fmt.Printf("Generate Type: %T\n", vi)
vp := GeneratePointer()
json.Unmarshal([]byte(`{}`), vp)
fmt.Printf("GenerateP Type: %T\n", vp)
}
Which outputs:
Generate Type: map[string]interface {}
GenerateP Type: *main.Test
I suggest you return a pointer in GenerateEmptyItem() instead of the actual struct value which is demonstrated in the GenerateP() example.
Playground Example
I've got the following method:
func ValidateParam(conf map[string]interface{}, paramName string, out interface{}) error {
param, ok := conf[paramName]
if !ok {
return errors.New("some error")
}
// ...
}
I would like to be able to call it like so:
myVar := "some text"
err := ValidateParam(conf, "my_var_param", &myVar)
myOtherVar := &MyStruct{}
err := ValidateParam(conf, "my_struct_param", myOtherVar)
The idea is:
Get the param using the conf map
Check that this param could be converted into the same type as out
Hydrate out using the param
=> It is kind of the same process as for json.Unmarshal(data, &myVar) or when doing a query with mgo query.Collection("col").One(&myVar)
I can't find how to achieve this, any help would be more than welcome.
Cheers
One option is to use the reflect package:
The basic idea is to create reflect.Values for input and output, check if input is assignable to output and then assign.
func ValidateParam(conf map[string]interface{}, paramName string, out interface{}) error {
param, ok := conf[paramName]
if !ok {
return errors.New("some error")
}
// Output is pointer to value.
vo := reflect.ValueOf(out)
if vo.Kind() != reflect.Ptr {
return errors.New("out must be poitner")
}
vo = vo.Elem() // deref ptr
// Can input be assigned to output?
vi := reflect.ValueOf(param)
if !vi.Type().AssignableTo(vo.Type()) {
return fmt.Errorf("param %s of type %v is not assignable to %v", paramName, vi.Type(), vo.Type())
}
vo.Set(vi)
return nil
}
playground example