access to struct field from struct method field - function

How I can access struct field Name from struct method field PrintName
example:
type Data struct {
Name string
PrintName func()
}
func main() {
data := Data{
Name: "Name",
PrintName: func() {
fmt.Println(Name)
},
}
data.PrintName()
}

The function value you assign to Data.PrintName has no connection to the enclosing struct whatsoever, so in the general case it can't access the Name field.
If you want to access the name field, you have to pass it explicitly, or use a closure that can access the data struct like this:
data := Data{
Name: "Name",
}
data.PrintName = func() {
fmt.Println(data.Name)
}
data.PrintName()
This will output (try it on the Go Playground):
Name
In this example the function value we assign to data.PrintName field is a closure because it uses a variable from the enclosing block.
You can also choose to pass the Data value explicitly to the function value, but then its signature must be modified:
type Data struct {
Name string
PrintName func(d Data)
}
func main() {
data := Data{
Name: "Name",
}
data.PrintName = func(d Data) {
fmt.Println(d.Name)
}
data.PrintName(data)
}
This outputs the same, try this one on the Go Playground.

Is there any particular reason you want PrintName as a struct field instead of just a method of Data?
type Data struct {
Name string
}
func (data *Data) PrintName(){
fmt.Println(data.Name)
}

Related

Create instance of struct via reflection and set values

what I try to do
I try to pass an instance of a struct - including json tags to a func, create a new instance, and set value on field
after this i try to serialize (JSON), but the values are empty
NOTICE: i looked up loads of articles on SO about setting values via reflection, but it seems i missed a little detail
struct definition
this part defines the struct with json and xml tags
type Person struct {
Name string `json:"Name" xml:"Person>FullName"`
Age int `json:"Age" xml:"Person>Age"`
}
create instance (+wrapping into empty interface)
afterwards I create an instance and store it in an interface{} - why? because in my production code this stuff will be done in a func which accepts a interface{}
var iFace interface{} = Person{
Name: "Test",
Age: 666,
}
creating a new instance of the struct and setting values via reflection
iFaceType := reflect.TypeOf(iFace)
item := reflect.New(iFaceType)
s := item.Elem()
if s.Kind() == reflect.Struct {
fName := s.FieldByName("Name")
if fName.IsValid() {
// A Value can be changed only if it is
// addressable and was not obtained by
// the use of unexported struct fields.
if fName.CanSet() {
// change value of N
switch fName.Kind() {
case reflect.String:
fName.SetString("reflectedNameValue")
fmt.Println("Name was set to reflectedNameValue")
}
}
}
fAge := s.FieldByName("Age")
if fAge.IsValid() {
// A Value can be changed only if it is
// addressable and was not obtained by
// the use of unexported struct fields.
if fAge.CanSet() {
// change value of N
switch fAge.Kind() {
case reflect.Int:
x := int64(42)
if !fAge.OverflowInt(x) {
fAge.SetInt(x)
fmt.Println("Age was set to", x)
}
}
}
}
}
Question
what am I doing wrong?
in production code I fill multiple copies with data and add it to a slice...
but this only makes sense if the json tags are kept in place and the stuff serializes just the same way.
code sample for play
package main
import (
"encoding/json"
"fmt"
"reflect"
)
func main() {
type Person struct {
Name string `json:"Name" xml:"Person>FullName"`
Age int `json:"Age" xml:"Person>Age"`
}
var iFace interface{} = Person{
Name: "Test",
Age: 666,
}
fmt.Println("normal: \n" + JSONify(iFace))
iFaceType := reflect.TypeOf(iFace)
item := reflect.New(iFaceType)
s := item.Elem()
if s.Kind() == reflect.Struct {
fName := s.FieldByName("Name")
if fName.IsValid() {
// A Value can be changed only if it is
// addressable and was not obtained by
// the use of unexported struct fields.
if fName.CanSet() {
// change value of N
switch fName.Kind() {
case reflect.String:
fName.SetString("reflectedNameValue")
fmt.Println("Name was set to reflectedNameValue")
}
}
}
fAge := s.FieldByName("Age")
if fAge.IsValid() {
// A Value can be changed only if it is
// addressable and was not obtained by
// the use of unexported struct fields.
if fAge.CanSet() {
// change value of N
switch fAge.Kind() {
case reflect.Int:
x := int64(42)
if !fAge.OverflowInt(x) {
fAge.SetInt(x)
fmt.Println("Age was set to", x)
}
}
}
}
}
fmt.Println("reflected: \n" + JSONify(item))
}
func JSONify(v interface{}) string {
var bytes []byte
bytes, _ = json.MarshalIndent(v, "", "\t")
return string(bytes)
}
Your item is of type reflect.Value. You have to call Value.Interface() to obtain the value wrapped in it:
fmt.Println("reflected: \n" + JSONify(item.Interface()))
With this change, output will be (try it on the Go Playground):
normal:
{
"Name": "Test",
"Age": 666
}
Name was set to reflectedNameValue
Age was set to 42
reflected:
{
"Name": "reflectedNameValue",
"Age": 42
}
reflect.Value itself is also a struct, but obviously trying to marshal it will not be identical to marshaling a Person struct value. reflect.Value does not implement marshaling the wrapped data to JSON.

How to set optional json in nest struct

I tried to set an optional json config in nest struct, when i need this json it will appear, otherwise it will not exist.
type Test struct {
Data NestTest `json:"data"`
}
type NestTest struct {
NestData1 string `json:"data1"`
NestData2 string `json:"data2,omitempty"`
}
test := Test{
Data: NestTest{
NestData1: "something",
},
}
b, err := json.Marshal(test)
fmt.Sprintf("the test struct json string is: %s", string(b))
output:
{"data":{"data1":"something","data2":""}}
expect:
{"data":{"data1":"something"}}
All fields are optional when unmarshalling (you won't get an error if a struct field doesn't have an associated value in the JSON). When marshaling, you can use omitempty to not output a field if it contains its type's zero value:
https://pkg.go.dev/encoding/json#Marshal
var Test struct {
Data string `json:"data,omitempty" validate:"option"`
}

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 show empty object instead of empty struct or nil in Go json marshal

I need to show json's empty object {} when do json.Marshal() for a struct pointer. I can only output either null value or empty struct value.
If the person key is filled with &Person{} or new(Person), it will show empty struct like below:
{
"data": {
"person": {
"name": "",
"age": 0
},
"created_date": "2009-11-10T23:00:00Z"
}
}
And if we don't initialize it at all, it will show null.
{
"data": {
"person": null,
"created_date": "2009-11-10T23:00:00Z"
}
}
I want to show "person": {}. Is it possible?
Go Playground for the complete code: https://play.golang.org/p/tT15G2ESPVc
Option A, use the omitempty tag option on all of the Person's fields and make sure the response's field is allocated before marshaling.
type Person struct {
Name string `json:"name,omitempty"`
Age int `json:"age,omitempty"`
}
// ...
resp.Person = new(Person)
https://play.golang.org/p/o3jWdru_8bC
Option B, use a non-pointer wrapper type that embeds the Person pointer type.
type PersonJSON struct {
*Person
}
type Response struct {
Person PersonJSON `json:"person"`
CreatedDate time.Time `json:"created_date"`
}
https://play.golang.org/p/EKQc7uf1_Vk
Option C, have the Reponse type implement the json.Marshaler interface.
func (r *Response) MarshalJSON() ([]byte, error) {
type tmp Response
resp := (*tmp)(r)
var data struct {
Wrapper struct {
*Person
} `json:"person"`
*tmp
}
data.Wrapper.Person = resp.Person
data.tmp = resp
return json.Marshal(data)
}
https://play.golang.org/p/1qkSCWZ225j
There may be other options...
In Go, an empty struct by definition assigns zero values to field elements. Eg: for int 0, "" for string, etc.
For your case, simply comparing to null would work out. Or, you could define an emptyPerson as:
var BAD_AGE = -1
emptyPerson := &Person{"", BAD_AGE} // BAD_AGE indicates no person
if person[age] == BAD_AGE {
// handle case for emptyPerson}

Golang multiple json tag names for one field

It it possible in Golang to use more than one name for a JSON struct tag ?
type Animation struct {
Name string `json:"name"`
Repeat int `json:"repeat"`
Speed uint `json:"speed"`
Pattern Pattern `json:"pattern",json:"frames"`
}
you can use multiple json tag with third part json lib like github.com/json-iterator/go
coding like below:
package main
import (
"fmt"
"github.com/json-iterator/go"
)
type TestJson struct {
Name string `json:"name" newtag:"newname"`
Age int `json:"age" newtag:"newage"`
}
func main() {
var json = jsoniter.ConfigCompatibleWithStandardLibrary
data := TestJson{}
data.Name = "zhangsan"
data.Age = 22
byt, _ := json.Marshal(&data)
fmt.Println(string(byt))
var NewJson = jsoniter.Config{
EscapeHTML: true,
SortMapKeys: true,
ValidateJsonRawMessage: true,
TagKey: "newtag",
}.Froze()
byt, _ = NewJson.Marshal(&data)
fmt.Println(string(byt))
}
output:
{"name":"zhangsan","age":22}
{"newname":"zhangsan","newage":22}
See How to define multiple name tags in a struct on how you can define multiple tags on one struct field.
You can also use a type Info map[string]interface{} instead of your struct.
Or you can use both types in your structure, and make method Details() which will return right pattern.
type Animation struct {
Name string `json:"name"`
Repeat int `json:"repeat"`
Speed uint `json:"speed"`
Pattern Pattern `json:"pattern"`
Frame Pattern `json:"frames"`
}
func (a Animation) Details() Pattern {
if a.Pattern == nil {
return a.Frame
}
return a.Pattern
}