How to make an addressable getter that casts from an interface - go

I have the concept of Context which is a map that can hold any structure. Basically, I want to create a generic getter that adddressably 'populates' the destination interface (similarly to how json decoding works).
Here's an example of how I want this to work:
type Context map[string]interface{}
// Random struct that will be saved in the context
type Step struct {
Name string
}
func main() {
stepA := &Step{Name: "Cool Name"}
c := Context{}
c["stepA"] = stepA
var stepB *Step
err := c.Get("stepA", stepB)
if err != nil {
panic(err)
}
fmt.Println(stepB.Name) // Cool Name
stepB.Name = "CoolName2"
fmt.Println(stepA.Name) // I want to say: CoolName2
}
func (c Context) Get(stepId string, dest interface{}) error {
context, ok := c[stepId]
if !ok {
return nil
}
destinationValue := reflect.ValueOf(dest)
contextValue := reflect.ValueOf(context)
destinationValue.Set(contextValue) // Errors here
return nil
}
I leaned towards using reflect, but maybe I don't need it? - so opened to other suggestions (except for generics as that complicates other matters) I'm getting the following error with the above:
panic: reflect: reflect.Value.Set using unaddressable value
You can test it here.

The argument passed to Get must be a pointer type whose element type is identical to the type in the context map. So if the value in the context map is of type *Step, then the argument's type must be **Step. Also the passed in argument cannot be nil, it can be a pointer to nil, but it itself cannot be nil.
So in your case you should do:
var stepB *Step
err := c.Get("stepA", &stepB) // pass pointer-to-pointer
if err != nil {
panic(err)
}
And the Get method, fixed up a bit:
func (c Context) Get(stepId string, dest interface{}) error {
context, ok := c[stepId]
if !ok {
return nil
}
dv := reflect.ValueOf(dest)
if dv.Kind() != reflect.Ptr || dv.IsNil() {
return errors.New("dest must be non-nil pointer")
}
dv = dv.Elem()
cv := reflect.ValueOf(context)
if dv.Type() != cv.Type() {
return errors.New("dest type does not match context value type")
}
dv.Set(cv)
return nil
}
https://go.dev/play/p/OECttqp1aVg

Related

Unmarshal method for complex object with go reflections

I'm starting to writing more complex go code, and my object node it to convert a list from a JSON object to a map with a particular key. This operation helps me to speed up my algorithm. But I have a problem now, my container struct has several complex JSON and I'm not able to write a generic solution to achieve a generic solution. The only way that I have in mind is to use a big switch case, but I think this is not the right solution.
This is my code at the moment, where the statusChannel is a map in the code but it is a list in the JSON string
type MetricOne struct {
// Internal id to identify the metric
id int `json:"-"`
// Version of metrics format, it is used to migrate the
// JSON payload from previous version of plugin.
Version int `json:"version"`
// Name of the metrics
Name string `json:"metric_name"`
NodeId string `json:"node_id"`
Color string `json:"color"`
OSInfo *osInfo `json:"os_info"`
// timezone where the node is located
Timezone string `json:"timezone"`
// array of the up_time
UpTime []*status `json:"up_time"`
// map of informatonof channel information
ChannelsInfo map[string]*statusChannel `json:"channels_info"`
}
func (instance *MetricOne) MarshalJSON() ([]byte, error) {
jsonMap := make(map[string]interface{})
reflectType := reflect.TypeOf(*instance)
reflectValue := reflect.ValueOf(*instance)
nFiled := reflectValue.Type().NumField()
for i := 0; i < nFiled; i++ {
key := reflectType.Field(i)
valueFiled := reflectValue.Field(i)
jsonName := key.Tag.Get("json")
switch jsonName {
case "-":
// skip
continue
case "channels_info":
// TODO convert the map[string]*statusChannel in a list of statusChannel
statusChannels := make([]*statusChannel, 0)
for _, value := range valueFiled.Interface().(map[string]*statusChannel) {
statusChannels = append(statusChannels, value)
}
jsonMap[jsonName] = statusChannels
default:
jsonMap[jsonName] = valueFiled.Interface()
}
}
return json.Marshal(jsonMap)
}
func (instance *MetricOne) UnmarshalJSON(data []byte) error {
var jsonMap map[string]interface{}
err := json.Unmarshal(data, &jsonMap)
if err != nil {
log.GetInstance().Error(fmt.Sprintf("Error: %s", err))
return err
}
instance.Migrate(jsonMap)
reflectValue := reflect.ValueOf(instance)
reflectStruct := reflectValue.Elem()
// reflectType := reflectValue.Type()
for key, value := range jsonMap {
fieldName, err := utils.GetFieldName(key, "json", *instance)
if err != nil {
log.GetInstance().Info(fmt.Sprintf("Error: %s", err))
if strings.Contains(key, "dev_") {
log.GetInstance().Info("dev propriety skipped if missed")
continue
}
return err
}
field := reflectStruct.FieldByName(*fieldName)
fieldType := field.Type()
filedValue := field.Interface()
val := reflect.ValueOf(filedValue)
switch key {
case "channels_info":
statusChannelsMap := make(map[string]*statusChannel)
toArray := value.([]interface{})
for _, status := range toArray {
var statusType statusChannel
jsonVal, err := json.Marshal(status)
if err != nil {
return err
}
err = json.Unmarshal(jsonVal, &statusType)
if err != nil {
return err
}
statusChannelsMap[statusType.ChannelId] = &statusType
}
field.Set(reflect.ValueOf(statusChannelsMap))
default:
field.Set(val.Convert(fieldType))
}
}
return nil
}
And when I will decode the object I receive the following error:
➜ go-metrics-reported git:(dev) ✗ make check
go test -v ./...
? github.com/OpenLNMetrics/go-metrics-reported/cmd/go-metrics-reported [no test files]
? github.com/OpenLNMetrics/go-metrics-reported/init/persistence [no test files]
=== RUN TestJSONSerializzation
--- PASS: TestJSONSerializzation (0.00s)
=== RUN TestJSONDeserializzation
--- FAIL: TestJSONDeserializzation (0.00s)
panic: reflect.Value.Convert: value of type map[string]interface {} cannot be converted to type *plugin.osInfo [recovered]
panic: reflect.Value.Convert: value of type map[string]interface {} cannot be converted to type *plugin.osInfo
goroutine 7 [running]:
testing.tRunner.func1.1(0x61b440, 0xc0001d69a0)
/home/vincent/.gosdk/go/src/testing/testing.go:1072 +0x30d
testing.tRunner.func1(0xc000001e00)
/home/vincent/.gosdk/go/src/testing/testing.go:1075 +0x41a
panic(0x61b440, 0xc0001d69a0)
/home/vincent/.gosdk/go/src/runtime/panic.go:969 +0x1b9
reflect.Value.Convert(0x6283e0, 0xc0001bb1a0, 0x15, 0x6b93a0, 0x610dc0, 0x610dc0, 0xc00014cb40, 0x196)
/home/vincent/.gosdk/go/src/reflect/value.go:2447 +0x229
github.com/OpenLNMetrics/go-metrics-reported/internal/plugin.(*MetricOne).UnmarshalJSON(0xc00014cb00, 0xc0001d8000, 0x493, 0x500, 0x7f04d01453d8, 0xc00014cb00)
/home/vincent/Github/OpenLNMetrics/go-metrics-reported/internal/plugin/metrics_one.go:204 +0x5b3
encoding/json.(*decodeState).object(0xc00010be40, 0x657160, 0xc00014cb00, 0x16, 0xc00010be68, 0x7b)
/home/vincent/.gosdk/go/src/encoding/json/decode.go:609 +0x207c
encoding/json.(*decodeState).value(0xc00010be40, 0x657160, 0xc00014cb00, 0x16, 0xc000034698, 0x54ec19)
/home/vincent/.gosdk/go/src/encoding/json/decode.go:370 +0x6d
encoding/json.(*decodeState).unmarshal(0xc00010be40, 0x657160, 0xc00014cb00, 0xc00010be68, 0x0)
/home/vincent/.gosdk/go/src/encoding/json/decode.go:180 +0x1ea
encoding/json.Unmarshal(0xc0001d8000, 0x493, 0x500, 0x657160, 0xc00014cb00, 0x500, 0x48cba6)
/home/vincent/.gosdk/go/src/encoding/json/decode.go:107 +0x112
github.com/OpenLNMetrics/go-metrics-reported/internal/plugin.TestJSONDeserializzation(0xc000001e00)
/home/vincent/Github/OpenLNMetrics/go-metrics-reported/internal/plugin/metric_one_test.go:87 +0x95
testing.tRunner(0xc000001e00, 0x681000)
/home/vincent/.gosdk/go/src/testing/testing.go:1123 +0xef
created by testing.(*T).Run
/home/vincent/.gosdk/go/src/testing/testing.go:1168 +0x2b3
FAIL github.com/OpenLNMetrics/go-metrics-reported/internal/plugin 0.008s
? github.com/OpenLNMetrics/go-metrics-reported/pkg/db [no test files]
? github.com/OpenLNMetrics/go-metrics-reported/pkg/graphql [no test files]
? github.com/OpenLNMetrics/go-metrics-reported/pkg/log [no test files]
? github.com/OpenLNMetrics/go-metrics-reported/pkg/utils [no test files]
FAIL
make: *** [Makefile:15: check] Error 1
can someone explain how I can do this operation in a generic way?
https://play.golang.org/p/jzU_lHj1wk7
type MetricOne struct {
// ...
// Have this field be ignored.
ChannelsInfo map[string]*statusChannel `json:"-"`
}
func (m MetricOne) MarshalJSON() ([]byte, error) {
// Declare a new type using the definition of MetricOne,
// the result of this is that M will have the same structure
// as MetricOne but none of its methods (this avoids recursive
// calls to MarshalJSON).
//
// Also because M and MetricOne have the same structure you can
// easily convert between those two. e.g. M(MetricOne{}) and
// MetricOne(M{}) are valid expressions.
type M MetricOne
// Declare a new type that has a field of the "desired" type and
// also **embeds** the M type. Embedding promotes M's fields to T
// and encoding/json will marshal those fields unnested/flattened,
// i.e. at the same level as the channels_info field.
type T struct {
M
ChannelsInfo []*statusChannel `json:"channels_info"`
}
// move map elements to slice
channels := make([]*statusChannel, 0, len(m.ChannelsInfo))
for _, c := range m.ChannelsInfo {
channels = append(channels, c)
}
// Pass in an instance of the new type T to json.Marshal.
// For the embedded M field use a converted instance of the receiver.
// For the ChannelsInfo field use the channels slice.
return json.Marshal(T{
M: M(m),
ChannelsInfo: channels,
})
}
// Same as MarshalJSON but in reverse.
func (m *MetricOne) UnmarshalJSON(data []byte) error {
type M MetricOne
type T struct {
*M
ChannelsInfo []*statusChannel `json:"channels_info"`
}
t := T{M: (*M)(m)}
if err := json.Unmarshal(data, &t); err != nil {
panic(err)
}
m.ChannelsInfo = make(map[string]*statusChannel, len(t.ChannelsInfo))
for _, c := range t.ChannelsInfo {
m.ChannelsInfo[c.ChannelId] = c
}
return nil
}

Whats the best way to get content from a generic and somehow dynamic go map?

I have this json that I convert to:
var leerCHAT []interface{}
but I am going through crazy hoops to get to any point on that map inside map and inside map crazyness, specially because some results are different content.
this is the Json
[
null,
null,
"hub:zWXroom",
"presence_diff",
{
"joins":{
"f718a187-6e96-4d62-9c2d-67aedea00000":{
"metas":[
{
"context":{},
"permissions":{},
"phx_ref":"zNDwmfsome=",
"phx_ref_prev":"zDMbRTmsome=",
"presence":"lobby",
"profile":{},
"roles":{}
}
]
}
},
"leaves":{}
}
]
I need to get to profile then inside there is a "DisplayName" field.
so I been doing crazy hacks.. and even like this I got stuck half way...
First is an array so I can just do something[elementnumber]
then is when the tricky mapping starts...
SORRY about all the prints etc is to debug and see the number of elements I am getting back.
if leerCHAT[3] == "presence_diff" {
var id string
presence := leerCHAT[4].(map[string]interface{})
log.Printf("algo: %v", len(presence))
log.Printf("algo: %s", presence["joins"])
vamos := presence["joins"].(map[string]interface{})
for i := range vamos {
log.Println(i)
id = i
}
log.Println(len(vamos))
vamonos := vamos[id].(map[string]interface{})
log.Println(vamonos)
log.Println(len(vamonos))
metas := vamonos["profile"].(map[string]interface{}) \\\ I get error here..
log.Println(len(metas))
}
so far I can see all the way to the meta:{...} but can't continue with my hacky code into what I need.
NOTICE: that since the id after Joins: and before metas: is dynamic I have to get it somehow since is always just one element I did the for range loop to grab it.
The array element at index 3 describes the type of the variant JSON at index 4.
Here's how to decode the JSON to Go values. First, declare Go types for each of the variant parts of the JSON:
type PrescenceDiff struct {
Joins map[string]*Presence // declaration of Presence type to be supplied
Leaves map[string]*Presence
}
type Message struct {
Body string
}
Declare a map associating the type string to the Go type:
var messageTypes = map[string]reflect.Type{
"presence_diff": reflect.TypeOf(&PresenceDiff{}),
"message": reflect.TypeOf(&Message{}),
// add more types here as needed
}
Decode the variant part to a raw message. Use use the name in the element at index 3 to create a value of the appropriate Go type and decode to that value:
func decode(data []byte) (interface{}, error) {
var messageType string
var raw json.RawMessage
v := []interface{}{nil, nil, nil, &messageType, &raw}
err := json.Unmarshal(data, &v)
if err != nil {
return nil, err
}
if len(raw) == 0 {
return nil, errors.New("no message")
}
t := messageTypes[messageType]
if t == nil {
return nil, fmt.Errorf("unknown message type: %q", messageType)
}
result := reflect.New(t.Elem()).Interface()
err = json.Unmarshal(raw, result)
return result, err
}
Use type switches to access the variant part of the message:
defer ws.Close()
for {
_, data, err := ws.ReadMessage()
if err != nil {
log.Printf("Read error: %v", err)
break
}
v, err := decode(data)
if err != nil {
log.Printf("Decode error: %v", err)
continue
}
switch v := v.(type) {
case *PresenceDiff:
fmt.Println(v.Joins, v.Leaves)
case *Message:
fmt.Println(v.Body)
default:
fmt.Printf("type %T not handled\n", v)
}
}
Run it on the playground.

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

Update array elements if the array in passed as &val and then converted to interface{}

I am trying to code some generic methods (CRUD approach) to share it between my services. The following example is a GetAll() method that returns all the documents present in my collection:
func GetAll(out interface{}) error {
// mongodb operations
// iterate through all documents
for cursor.Next(ctx) {
var item interface{}
// decode the document
if err := cursor.Decode(&item); err != nil {
return err
}
(*out) = append((*out), item)
// arrays.AppendToArray(out, item) // Read below :)
}
return nil // if no error
}
I also tried with some reflection, but then:
package arrays
import "reflect"
func AppendToArray(slicePtrInterface interface{}, item interface{}) {
// enter `reflect`-land
slicePtrValue := reflect.ValueOf(slicePtrInterface)
// get the type
slicePtrType := slicePtrValue.Type()
// navigate from `*[]T` to `T`
_ = slicePtrType.Elem().Elem() // crashes if input type not `*[]T`
// we'll need this to Append() to
sliceValue := reflect.Indirect(slicePtrValue)
// append requested number of zeroes
sliceValue.Set(reflect.Append(sliceValue, reflect.ValueOf(item)))
}
panic: reflect.Set: value of type primitive.D is not assignable to type *mongodb.Test [recovered]
panic: reflect.Set: value of type primitive.D is not assignable to type *mongodb.Test
What I would like is to get the same approach as cursor.Decode(&item) (you can see above)
Here's how to do it:
// GetAll decodes the cursor c to slicep where slicep is a
// pointer to a slice of pointers to values.
func GetAll(ctx context.Context, c *Cursor, slicep interface{}) error {
// Get the slice. Call Elem() because arg is pointer to the slice.
slicev := reflect.ValueOf(slicep).Elem()
// Get value type. First call to Elem() gets slice
// element type. Second call to Elem() dereferences
// the pointer type.
valuet := slicev.Type().Elem().Elem()
// Iterate through the cursor...
for c.Next(ctx) {
// Create new value.
valuep := reflect.New(valuet)
// Decode to that value.
if err := c.Decode(valuep.Interface()); err != nil {
return err
}
// Append value pointer to slice.
slicev.Set(reflect.Append(slicev, valuep))
}
return c.Err()
}
Call it like this:
var data []*T
err := GetAll(ctx, c, &data)
if err != nil {
// handle error
}
Run it on the Go Playground.
Here's a generalization of the code to work with non-pointer slice elements:
func GetAll(ctx context.Context, c *Cursor, slicep interface{}) error {
slicev := reflect.ValueOf(slicep).Elem()
valuet := slicev.Type().Elem()
isPtr := valuet.Kind() == reflect.Ptr
if isPtr {
valuet = valuet.Elem()
}
for c.Next(ctx) {
valuep := reflect.New(valuet)
if err := c.Decode(valuep.Interface()); err != nil {
return err
}
if !isPtr {
valuep = valuep.Elem()
}
slicev.Set(reflect.Append(slicev, valuep))
}
return c.Err()
}

Converting interface into another and copy content

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

Resources