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

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)

Related

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

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.

XML Unmarshal with dynamic type (empty interface) in Go

I need to parse an XML message that has a dynamic element, so I have used an element of type interface{} to represent it in my Message struct.
Once I know the type of this dynamic element (at runtime) I initialise a message struct and then try to unmarshal the XML message. However, the contents of the dynamic element are not unmarshalled.
Here is a Go Playground with what I'm trying to achieve, with comments and the actual vs expected outputs: https://play.golang.org/p/eKVetUPmVI2
I tried several variations but can't get the unmarshalling to work as expected. Can anyone help me to understand why this behaviour and how to make it work? Thanks in advance.
Code (in case the Go Playground link breaks one day):
package main
import "fmt"
import "encoding/xml"
// XML root
type Message struct {
XMLName xml.Name `xml:"message"`
Operation Operation `xml:"operation"`
}
// An Operation can contain either a Create or an Update element
type Operation struct {
Create *Create `xml:"create"`
Update *Update `xml:"update"`
}
// Doesn't matter...
type Create struct{}
// Update contains a Color element or Any other element (we only know its type during runtime)
type Update struct {
Color *Color `xml:"color"`
Other Any
}
// Doesn't matter...
type Color struct{}
type Any interface{}
var xmlStr = []byte(`<message>
<operation>
<update>
<size>
<width>1000</width>
</size>
</update>
</operation>
</message>`)
func main() {
// At this point we already know what to expect to receive in Other, so we can declare a struct for its content (Size)
type Size struct {
XMLName xml.Name `xml:"size"`
Width string `xml:"width"`
}
// Unmarshal
msg := &Message{
Operation: Operation{
Update: &Update{
Other: &Size{}, // Here I'm setting Other to Size, so I would expect Go to unmarshal the <size> contents into it
},
},
}
if err := xml.Unmarshal(xmlStr, msg); err != nil {
fmt.Println(err)
}
// Marshal again
b, err := xml.MarshalIndent(msg, "", " ")
if err != nil {
fmt.Println(err)
}
fmt.Printf("expected:\n\n%s\n\n", xmlStr)
fmt.Printf("actual:\n\n%s", string(b))
}
Per the encoding/xml package documentation:
If the XML element contains a sub-element that hasn't matched any
of the above rules and the struct has a field with tag ",any",
unmarshal maps the sub-element to that struct field.
One small update to your code makes it work as you expected:
Add the xml:",any" tag to your Other field definition.
To clean up the code, I would also remove the Any type, you don't need it. You can change the Other field definition to type interface{} with tag xml:",any" and accomplish the same thing.
Like so:
Other interface{} `xml:",any"`
Execute and see the "1000" captured.
I suggest updating your question to include your code directly to make it easier for people to find/search/read your question. Having the Go playground link as well is also useful so readers can quickly run/tweak/test the example.

How to create struct runtime in golang

For example, I have a struct that I take from the outside. I do not know the struct in fields and field values. I want to copy and use the same struct.
With reflection I find the fields and types in it. But how do I create this struct in the runtime?
Edit : I just want to create a struct in the same name as the runtime. Imagine I do not know my person type. I just want to create the same struct by reflection with interface.
I only know one interface. Person struct I just created it for instance. When a person creates a struct and sends it out, I will create it. instead of person, customer, student etc. You can send.
consider the following code as a 3rd party library.
package main
import(
"fmt"
"reflect"
)
type Person struct {
Id int
Name string
Surname string
}
func main(){
person := NewSomething()
newPerson := typeReflection(person)
ChangePerson(newPerson)
fmt.Println("Success")
}
func typeReflection(_person interface{}){
val := reflect.ValueOf(_person)
//How to create same struct
}
The github.com/mitchellh/copystructure library handles this operation, which is known as a deep copy. After performing a deep copy, the original and the copy contain the same data but modifications to either one do not affect the other.
package main
import (
"fmt"
"github.com/mitchellh/copystructure"
)
type Person struct {
Id int
Name string
Surname string
}
func main() {
original := Person{Id: 0, Name: "n", Surname: "s"}
copy := deepCopy(original)
// Change fields of the original Person.
original.Id = 9
fmt.Printf("original: %#v\n", original)
// The copy of the Person has not change, still has Id:0.
fmt.Printf("copy: %#v\n", copy)
}
func deepCopy(original interface{}) interface{} {
copy, err := copystructure.Copy(original)
if err != nil {
panic(err)
}
return copy
}

Golang assign a map to an interface

I wants to assign a map to an interface where underlying value is of type map[string]interface.
type Data struct{
data interface{}
}
result := make(map[string]interface{})
data := Data{
data:result
}
details := make(map[string]interface{})
details["CreatedFor"] = "dfasfasdf"
details["OwnedBy"] = "fasfsad"
How can I insert details value to data interface inside Data struct ?
To be able to treat an interface as a map, you need to type check it as a map first.
I've modified your sample code a bit to make it clearer, with inline comments explaining what it does:
package main
import "fmt"
func main() {
// Data struct containing an interface field.
type Data struct {
internal interface{}
}
// Assign a map to the field.
type myMap map[string]interface{}
data := Data{
internal: make(myMap),
}
// Now, we want to access the field again, but as a map:
// check that it matches the type we want.
internalMap, ok := data.internal.(myMap)
if !ok {
panic("data.internal is not a map")
}
// Now what we have the correct type, we can treat it as a map.
internalMap["CreatedFor"] = "dfasfasdf"
internalMap["OwnedBy"] = "fasfsad"
// Print the overall struct.
fmt.Println(data)
}
This outputs:
{map[CreatedFor:dfasfasdf OwnedBy:fasfsad]}

Obtaining the name of a known struct field

I have a struct which represents an object in a database, something like:
type Object struct {
Id string
Field1 string
Field2 int
}
And I'd like to have a function that updates the specific field in the database whenever the field is modified, something along these lines:
func (self *Object) SetField1(value string) {
self.Field1 = value
database.Update(self.Id, "Field1", self.Field1) // pseudocode
}
Is there a way to replace the "Field1" hard-coded string such that my code is resistant to future changes in the struct field ordering and naming?
I've poked around the reflect package, and it would be nice to be able to get the StructField that represents the field I'm working with, but it seems to require either the name of the field via hard-coded string, or the field's index in the struct (which is subject to change).
not in the context that you're talking about. It's not passed in as a parameter, so you need some other way of specifying which of the struct's fields to be sent. The mental gap here is you're trying to treat that set function like it's a property when it's not; the key difference between a property, as seen in other languages, is that a property is bound to a specific field, whereas your SetField1 method is bound to the whole struct. That method might as well set two fields.
Generally if you're doing field-wise reflective stuff, and you want to do fancy dynamic stuff with fields, you want to use struct tags. e.g., like this:
type Object struct {
Id string `db:"id"`
Field1 string `db:"field1"`
Field2 int `db:"field2"`
}
you can access those tags like-a-this:
package main
import (
"reflect"
"fmt"
)
func main() {
var x struct {
MyField int `core:"required"`
}
t := reflect.TypeOf(x)
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fmt.Println(field.Name, ":", field.Tag.Get("core"))
}
}
that's not really a full solution, but... the question in the way it's asked is impossible to do.
You could validate your string by adding a method something like this:
func (self *Object) verify(field string) string {
if _, ok := reflect.TypeOf(*self).FieldByName(field); ok {
return field
}
panic("Invalid field name")
}
And then use it when passing the string to the database update
func (self *Object) SetField1(value string) {
self.Field1 = value
database.Update(self.Id, self.verify("Field1"), self.Field1) // pseudocode
}
But I would think that if you're willing to use reflection, that you'd be better off just making a generic setField method that accepts the field as a string, and the value as a interface{}, checks the field and value, sets the value and updates the database.
This way everything is done using the string, so it'll either work or panic, and you don't need to remember to use the .verify() method.
Somthing like:
func (self *Object) SetField(field string, value interface{}) {
// verify field and value using reflection
// set the value using reflection
database.Update(self.Id, field, self.Field1)
}
Though I don't think this'll work on unexported fields.

Resources