Marshal different sets of fields from a struct at runtime in Golang - go

Lets say I have a struct like this (dummy example) :
type Animal struct {
name string
weight int
isLandAnimal boolean
Family
}
type Family struct {
name string
type string
}
Now what I want to achieve is something like this :
var a Animal = ....
bys, err := json.Marshal(a, []string{"name", "Family.name"})
and once printed bys should look like this :
{
"name": "Lion"
"family" : {
"name" : "Felines"
}
}
So I can pass a string slice that references which fields I want to actually get marshaled in the final string.
Does something like this exists in the standard library or in a third party one?
NOTE: This is what I could do based on another SO answer:
https://go.dev/play/p/XVb83zoXpmb
Will this bring troubles down the road?

You can annotate the fields which let you specify how it should show up in json and if it needs to be omitted when empty. https://pkg.go.dev/encoding/json#Marshal
Here is a working code. https://pkg.go.dev/encoding/json#Marshal
package main
import (
"encoding/json"
"fmt"
)
type Animal struct {
Name string `json:"name,omitempty"`
Weight int `json:"weight,omitempty"`
IsLandAnimal bool `json:"IsLandAnimal,omitempty"`
Family Family `json:"Family,omitempty"`
}
type Family struct {
Name string `json:"Name,omitempty"`
Familytype string `json:"Familytype,omitempty"`
}
func main() {
a := Animal{Family: Family{Name: "Felines"}, Name: "Lion"}
bys, _ := json.Marshal(a)
fmt.Println(string(bys))
}

What you're looking for is struct field annotations for json.Marshal. Basically go expects you to explicitly define the mapping from a struct to your export object, in this case json.
You need to ensure that the fields in your struct are exported, or basically start with a capital letter.
Next you can add a annotation to the end of the field to tell go what to look for when unmarshalling, or how to marshal that specific field into the file type format you're looking for.
For example:
This struct with exported members and annotations:
type Cookie struct {
Name string `json:"name"`
Ingredients int `json:"ingredients"`
MilkDunkTime int `json:"milk_dunk_time"`
}
would export into this json:
{
"name": "",
"ingredients: 0,
"milk_dunk_time:" 0
}
I've provided a playground link for your example above with some minor tweaks.

Related

How to merge 2 structs

I am trying to modify some stuff in Go. I have the following struct I created
I have an API call returning something like this
MyStruct struct {
DID string `bson:"d_id" json:"d_id"`
PID string `bson:"p_id" json:"p_id"`
...
}
in one call in the code, I want to append a new key to that struct
like
myNewStruct {
DID string `bson:"d_id" json:"d_id"`
PID string `bson:"p_id" json:"p_id"`
...
MyNewKey string `bson:"new_key" json:"new_key"`
}
The thing is, I want to add a new key, and keep the rest at the root of the object, like the object above. without having to rewrite the full object structure, or doing a for loop of each key.
type MyNewStruct struct {
*MyStruct
MyNewKey MyValueType
}
I have, two variable with the data,
MyStructData and MyNewKeyData
I want to, but don t know how to merge those two data inside MyNewStruct so that everything in MyStructData will be at the root of the key, and everything in MyNewKeyData will be indise the key MyNewKey
I am trying stuff like
theObjectIWant := MyNewStruct {
MyStructData,
"MyNewKey" : MyNewKeyData
}
but doesn't work
When you create an anonymous member in a struct, the compiler generates a name for the member that is named the same as the type. You can use this name when initializing the containing struct.
Here is a simplified example
type MyStruct struct {
DID string
PID string
}
type MyNewStruct struct {
MyStruct
MyNewKey string
}
ms := MyStruct{
DID: "did",
PID: "pid",
}
m := MyNewStruct{
MyStruct: ms,
MyNewKey: "test",
}
I'm not sure I fully understand what your looking for, but this may help.
type MyStruct struct {
DID string
PID string
}
type myNewStruct struct {
MyStruct
newKey string
}
func main() {
s1 := MyStruct{DID: `bson:"d_id" json:"d_id"`}
s2 := myNewStruct{MyStruct: s1, newKey: `bson:"new_key" json:"new_key"`}
fmt.Println(s2.DID)
fmt.Println(s2.newKey)
}

How to pass a pointer of struct field in mapstructure.decode

I am trying to decode a map into a struct type with help of mapstructure library. If I do it with plain variable it decodes ok, but if I pass struct field it does not decode the map:
package main
import (
"github.com/mitchellh/mapstructure"
)
type Person struct {
Name string
}
type Bundle struct {
Name string
Struct interface{}
}
func main() {
p_map := map[string]string{
"Name": "John",
}
p := Person{}
mapstructure.Decode(p_map, &p)
print(p.Name) // shows name John
b := Bundle{
"person"
Person{},
}
mapstructure.Decode(p_map, &b.Struct)
print(b.Struct.(Person).Name) // Does not show any name. Blank
}
Could you please clarify if I am passing wrong storage for map decoding or it is just mapstructure limitation and I am not able to decode maps into struct fields? Thank you!
UPD
I am sorry if I was not clear enough about the actual reason I need to use such flow:
I send HTTP requests to different resources and get various objects with different fields so initially I collect them as interface{}. After I get a particular resource object, I need to convert it into a particular struct (Person in my sample) so I use mapstructure.decode() function for that.
As I have various objects that are decoded in different structures I want to create a loop in order to avoid code duplication. What I wanted to do is to create a slice with different structures like:
bundles := []Bundle{
{"person", Person{}}
{"employee", Employee{}}
...
}
And then decode objects in a loop:
for bundle := range bundles {
// map_storage contains different type maps that are going to be decoded into struct and key for the specific object is bundle name
mapstructure.Decode(maps_storage[bundle.Name], &bundle.Struct)
// bundle.Struct blank, but I expect that it is filled as in the example below
}
I think you must slightly change the implementation
var p1 Person
mapstructure.Decode(p_map, &p1)
b := Bundle{
p1,
}
print(b.Struct.(Person).Name) // John will appear
I'm trying your code above but lead to empty Person. Maybe Decode function cannot change real value of b.Struct(I'm not sure the exact reason, this is just my opinion), but if I decode to struct Person first then assign to Bundle that works.
Updated:
with some research, I found out the problem.You must use pointer instead of struct. here the updated code
package main
import (
"github.com/mitchellh/mapstructure"
)
type Person struct {
Name string
}
type Bundle struct {
Name string
Struct interface{}
}
func main() {
p_map := map[string]string{
"Name": "John",
}
p := &Person{}
mapstructure.Decode(p_map, &p)
print(p.Name) // shows name John
b := Bundle{
"person",
&Person{},
}
mapstructure.Decode(p_map, &b.Struct)
print(b.Struct.(*Person).Name) // Does not show any name. Blank
}
After changing the type of Struct field in the Bundle from interface{} to Person, It worked for me.
type Bundle struct {
Struct Person
}
print(b.Struct.Name)

using a map which store struct field name and type to recover Go struct

Playground for my problem: https://play.golang.org/p/rVwEaxpkJGL
I have struct like
type SimpleBbInput struct {
MyInput struct {
Num struct {
val int
}
}
HisInput string
HerInput uint8
}
I will store the field name and type in a map[string]interface{}, result like
map[HerInput:uint8 MyInput:map[Num:map[val:int]] HisInput:string]
My question is how to using this map to get back SimpleBbInput without knowing this struct.
Thanks

Merging two similar structs of different types

I have the following structs...
type Menu struct {
Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"`
Name string `protobuf:"bytes,2,opt,name=name" json:"name,omitempty"`
Description string `protobuf:"bytes,3,opt,name=description" json:"description,omitempty"`
Mixers []*Mixer `protobuf:"bytes,4,rep,name=mixers" json:"mixers,omitempty"`
Sections []*Section `protobuf:"bytes,5,rep,name=sections" json:"sections,omitempty"`
}
And...
type Menu struct {
ID bson.ObjectId `json:"id" bson:"_id"`
Name string `json:"name" bson:"name"`
Description string `json:"description" bson:"description"`
Mixers []Mixer `json:"mixers" bson:"mixers"`
Sections []Section `json:"sections" bson:"sections"`
}
I basically need to convert between the two struct types, I've attempted to use mergo, but that can only merge structs that are assignable to one another. The only solution I have so far is iterating through each struct, converting the ID by re-assigning it and converting its type between string and bson.ObjectId. Then iterating through each map field and doing the same. Which feels like an inefficient solution.
So I'm attempting to use reflection to be more generic in converting between the two ID's. But I can't figure out how I can effectively merge all of the other fields that do match automatically, so I can just worry about converting between the ID types.
Here's the code I have so far...
package main
import (
"fmt"
"reflect"
"gopkg.in/mgo.v2/bson"
)
type Sub struct {
Id bson.ObjectId
}
type PbSub struct {
Id string
}
type PbMenu struct {
Id string
Subs []PbSub
}
type Menu struct {
Id bson.ObjectId
Subs []Sub
}
func main() {
pbMenus := []*PbMenu{
&PbMenu{"1", []PbSub{PbSub{"1"}}},
&PbMenu{"2", []PbSub{PbSub{"1"}}},
&PbMenu{"3", []PbSub{PbSub{"1"}}},
}
newMenus := Serialise(pbMenus)
fmt.Println(newMenus)
}
type union struct {
PbMenu
Menu
}
func Serialise(menus []*PbMenu) []Menu {
newMenus := []Menu{}
for _, v := range menus {
m := reflect.TypeOf(*v)
fmt.Println(m)
length := m.NumField()
for i := 0; i < length; i++ {
field := reflect.TypeOf(v).Field(i)
fmt.Println(field.Type.Kind())
if field.Type.Kind() == reflect.Map {
fmt.Println("is map")
}
if field.Name == "Id" && field.Type.String() == "string" {
// Convert ID type
id := bson.ObjectId(v.Id)
var dst Menu
dst.Id = id
// Need to merge other matching struct fields
newMenus = append(newMenus, dst)
}
}
}
return newMenus
}
I'm can't just manually re-assign the fields because I'm hoping to detect maps on the structs fields and recursively perform this function on them, but the fields won't be the same on embedded structs.
Hope this makes sense!
I think that it is probably better to write your own converter, because you will always have some cases that are not covered by existing libs\tools for that.
My initial implementation of it would be something like this: basic impl of structs merger

Copy one struct to another where structs have same members and different types

I have two struct having the same members, I want to copy one struct to another, see the pseudo code below:
type Common struct {
Gender int
From string
To string
}
type Foo struct {
Id string
Name string
Extra Common
}
type Bar struct {
Id string
Name string
Extra Common
}
Then I have foo of struct Foo, and bar of struct Bar, Is there any way to copy bar from foo?
Use a conversion to change the type. The following code uses a conversion to copy a value of type Foo to a value of type Bar:
foo := Foo{Id: "123", Name: "Joe"}
bar := Bar(foo)
playground example
The conversion only works when the underlying types are identical except for struct tags.
https://github.com/jinzhu/copier (same author of gorm) is also quite a good one, I have nested structs and all I do is:
copier.Copy(&employees, &user)
works great
If you would like to copy or clone to a different struct, I would suggest using deepcopier
It provides nice features like skipping, custom mapping, and forcing. below is an example from github:
Install:
go get -u github.com/ulule/deepcopier
Example:
package main
import (
"fmt"
"github.com/ulule/deepcopier"
)
// Model
type User struct {
// Basic string field
Name string
// Deepcopier supports https://golang.org/pkg/database/sql/driver/#Valuer
Email sql.NullString
}
func (u *User) MethodThatTakesContext(ctx map[string]interface{}) string {
// do whatever you want
return "hello from this method"
}
// Resource
type UserResource struct {
//copy from field "Name"
DisplayName string `deepcopier:"field:Name"`
//this will be skipped in copy
SkipMe string `deepcopier:"skip"`
//this should call method named MethodThatTakesContext
MethodThatTakesContext string `deepcopier:"context"`
Email string `deepcopier:"force"`
}
func main() {
user := &User{
Name: "gilles",
Email: sql.NullString{
Valid: true,
String: "gilles#example.com",
},
}
resource := &UserResource{}
deepcopier.Copy(user).To(resource)
//copied from User's Name field
fmt.Println(resource.DisplayName)//output: gilles
fmt.Println(resource.Email) //output: gilles#example.com
fmt.Println(resource.MethodThatTakesContext) //output: hello from this method
}
Also, some other way you could achieve this is by encoding the source object to JSON and then decode it back to the destination object.
This is another possible answer
type Common struct {
Gender int
From string
To string
}
type Foo struct {
Id string
Name string
Extra Common
}
type Bar struct {
Id string
Name string
Extra Common
}
foo:=Foo{
Id:"123",
Name:"damitha",
Extra: struct {
Gender int
From string
To string
}{Gender:1 , From:"xx", To:"yy" },
}
bar:=*(*Bar)(unsafe.Pointer(&foo))
fmt.Printf("%+v\n",bar)
If you would like to copy or clone to a different struct, I would suggest using deepcopier
It provides nice features like skipping, custom mapping, and forcing.
You can achieve nested struct copy following way.
Install:
go get -u github.com/ulule/deepcopier
Example:
package main
import (
"fmt"
"strconv"
"github.com/ulule/deepcopier"
)
//FieldStruct - Field Struct
type FieldStruct struct {
Name string `deepcopier:"field:TargetName"`
Type string `deepcopier:"field:TargetType"`
}
//SourceStruct - Source Struct
type SourceStruct struct {
Name string `deepcopier:"field:TargetName"`
Age int `deepcopier:"field:TargetAge"`
StringArray []string `deepcopier:"field:TargetStringArray"`
StringToInt string `deepcopier:"context"`
Field FieldStruct
Fields []FieldStruct
}
//TargetFieldStruct - Field Struct
type TargetFieldStruct struct {
TargetName string
TargetType string
}
//TargetStruct - Target Struct
type TargetStruct struct {
TargetName string
TargetAge int
TargetStringArray []string
TargetInt int
TargetField TargetFieldStruct
TargetFields []TargetFieldStruct
}
//write methods
//TargetInt - StringToInt
func (s *SourceStruct) TargetInt() int {
i, _ := strconv.Atoi(s.StringToInt)
return i
}
func main() {
s := &SourceStruct{
Name: "Name",
Age: 12,
StringArray: []string{"1", "2"},
StringToInt: "123",
Field: FieldStruct{
Name: "Field",
Type: "String",
},
Fields: []FieldStruct{
FieldStruct{
Name: "Field1",
Type: "String1",
},
FieldStruct{
Name: "Field2",
Type: "String2",
},
},
}
t := &TargetStruct{}
//coping data into inner struct
deepcopier.Copy(&t.TargetField).From(&s.Field)
// copied array of Struct
for i := range s.Fields {
// init a struct
t.TargetFields = append(t.TargetFields, TargetFieldStruct{})
// coping the data
deepcopier.Copy(&t.TargetFields[i]).From(&s.Fields[i])
}
//Top level copy
deepcopier.Copy(t).From(s)
fmt.Println(t)
}
Output:
&{Name 12 [1 2] 123 {Field String} [{Field1 String1} {Field2 String2}]}

Resources