Issue translating data back from serialization into Go struct dynamically using reflection - google-app-engine

I'm having trouble using reflection in Go to fetch data from a cache dynamically into various statically declared struct types:
func FetchFromCacheOrSomewhereElse(cacheKey string, returnType reflect.Type) (out interface {}, err error) {
fetchFromCache := reflect.New(returnType).Interface();
_, err=memcache.Gob.Get(*context, cacheKey, &fetchFromCache);
if (err==nil) {
out=reflect.ValueOf(fetchFromCache).Elem().Interface();
} else if (err==memcache.ErrCacheMiss) {
/* Fetch data manually... */
}
return out, err;
}
It seems that reflect won't translate this statically typed cache data back into a reflect value, and returns this error instead: gob: local interface type *interface {} can only be decoded from remote interface type; received concrete type ... :\
This data is saved elsewhere in the code to the cache without the need for reflect.

memcache.Gob.Get() which is Codec.Get() expects the "target" as a pointer, wrapped into an interface{}.
Your fetchFromCache is already just that: a pointer to a value of the specified type (returnType) wrapped in an interface{}. So you don't need to take its address when passing it to Gob.Get(): pass it as-is:
_, err=memcache.Gob.Get(*context, cacheKey, fetchFromCache)

Related

Generic Parameter T could not be inferred with optional generic array

I have a method in my Model class that has signature below:
func parse<T: Codable>(data: Data) throws -> Array<T>?
When I call the method in another class, Facade, I get the
Generic Parameter T could not be inferred
Calling function as below
if let data = data {
do{
let parsedArray = try self.model.parse(data: data);
}
catch{
print(error)
}
gives me the compiler warning on the line where I call the parse function.
You need to explicitly declare the type of the variable you are setting or add another parameter to the parse method and pass the desired type:
let parsedArray: [YourType] = try model.parse(data: data)

type interface {} does not support indexing in golang

I have such map:
Map := make(map[string]interface{})
This map is supposed to contain mapping from string to array of objects. Arrays can be of different types, like []Users or []Hosts. I populated this array:
TopologyMap["Users"] = Users_Array
TopologyMap["Hosts"] = Hosts_Array
but when I try to get an elements from it:
Map["Users"][0]
it gives an error:
(type interface {} does not support indexing)
How can I overcome it?
You have to explicitly convert your interface{} to a slice of the expected type to achieve it. Something like this:
package main
import "fmt"
type Host struct {
Name string
}
func main() {
Map := make(map[string]interface{})
Map["hosts"] = []Host{Host{"test.com"}, Host{"test2.com"}}
hm := Map["hosts"].([]Host)
fmt.Println(hm[0])
}
Playground link
First thing to be noted is the interface{} can hold any data type including function and struct or []struct. Since the error gives you :
(type interface {} does not support indexing)
It means that it holds no slice or no array values. Because you directly call the index in this case is 0 to an interface{} and you assume that the Map["Users"] is an array. But it is not. This is one of very good thing about Go it is statically type which mean all the data type is check at compiled time.
if you want to be avoid the parsing error like this:
panic: interface conversion: interface {} is []main.User, not
[]main.Host
To avoid that error while your parsing it to another type like Map["user"].([]User) just in case that another data type pass to the interface{} consider the code snippet below :
u, ok := myMap["user"].([]User)
if ok {
log.Printf("value = %+v\n", u)
}
Above code is simple and you can use it to check if the interface match to the type you are parsing.
And if you want to be more general passing the value to your interface{} at runtime you can check it first using reflect.TypeOf() please consider this code :
switch reflect.TypeOf(myMap["user"]).String() {
case "[]main.User":
log.Println("map = ", "slice of user")
logger.Debug("map = ", myMap["user"].([]User)[0])
case "[]main.Host":
log.Println("map = ", "slice of host")
logger.Debug("map = ", myMap["user"].([]Host)[0])
}
after you know what's the value of the interface{} you can confidently parse it the your specific data type in this case slice of user []User. Not that the main there is a package name you can change it to yours.
This is how I solved it for unstructured data. You have to parse to index string until you reach the end. Then you can print key value pairs.
yamlStr := `
input:
bind: 0.0.0.0:12002
interface: eth0
reaggregate: {}
versions: {}
output:
destination: owl-opsw-sw-dev-4.opsw:32001
interface: eth0
`
var obj map[string]interface{}
if err := yaml.Unmarshal([]byte(yamlStr), &obj); err != nil {
// Act on error
}
// Set nested object to map[string]
inputkv := streamObj["input"].(map[string]interface{})
for key, value := range inputkv {
// Each value is an interface{} type, that is type asserted as a string
fmt.Println(key, value.(string))
}
Result
bind 0.0.0.0:12002
interface eth0

save and loading custom types

I have a custom type map[string]map[string]string am trying to save in Google Datastore, the save works as expected. However the load functions complains about assignment to entry in nil map
//Address represents address
type Address map[string]map[string]string
Type above is a map of map[string]string, target at saving different address types.
//Load function from PropertyLoaderInterface helps datastore load this object
func (a *Address) Load(dp []datastore.Property) error {
for _, property := range dp {
(*a)[property.Name] = util.InterfaceToMapString(property.Value)
}
return nil
}
In the load function I deference the Address which is a map of map[string]string, it saves the following example JSON format.
"Company":{
"physicalAddress": "",
"postalAddress": "",
"physicalCity": "",
"postalCity": "",
"physicalCode": "",
"postalCode": "",
"physicalCountry": "",
"postalCountry": ""
}
Save function below works well and data is store in datastore. The Load is however rather a tricky bugger.
//Save function from PropertyLoaderInterface helps datastore save this object
func (a *Address) Save() ([]datastore.Property, error) {
propertise := []datastore.Property{}
for name, value := range *a {
propertise = append(propertise, datastore.Property{Name: name,
NoIndex: true,
Value: util.MapToJSONString(value)})
}
return propertise, nil
}
Working load for Address struct
func (a *Address) Load(dp []datastore.Property) error {
*a = make(Address)
for _, property := range dp {
(*a)[property.Name] = util.InterfaceToMapString(property.Value)
}
return nil
}
First, regarding the declarations - https://stackoverflow.com/a/42901715/4720042
Next, I feel you should instead use a custom struct for this purpose.
Even if you still want to use a map[string]map[string]string, you cannot assign to a field in a map that hasn't been explicitly defined viz. property.Name
You have to initialize that map with make if you plan on adding the elements later.

How to marshal an empty struct as an empty array

I'm not sure if the title accurately explains what I'm looking to do so I'll try to give as many details as I can.
I have a struct with nested structs that I am marshalling and sending out to an API. There are some requests that require my lowest level struct to be empty and I need its parent parameter to equal an empty array instead of null. If I use omitempty on the parameter, it will completely remove it from my request and the request will fail. If I use omitempty on the parameter's parameters, it causes the value to be null and the request will fail.
Here are the structs I am using for the request:
// SubscribeRequest is the top level wrapper for ICWS request bodies
SubscribeRequest struct {
ClientStateIsFresh bool `json:"clientStateIsFresh"`
StatisticKeys []StatisticKey `json:"statisticKeys"`
}
// StatisticKey is a value we want to pull from ICWS reporting
StatisticKey struct {
StatisticIdentifier string `json:"statisticIdentifier"`
ParameterValueItems []Parameter `json:"parameterValueItems"`
}
// Parameter is a filter applied when pulling statistics
Parameter struct {
ParameterTypeID string `json:"parameterTypeId"`
Value string `json:"value"`
}
And I need the marshalled JSON to look like this:
{
"clientStateIsFresh":true,
"statisticKeys":
[
{
"statisticIdentifier":"inin.system.interaction:ActiveCalls",
"parameterValueItems":
[
]
}
]
}
If I have anything other than this, the request fails. I don't get any errors, but it doesn't return any usable data. Any suggestions on how to accomplish this?
*Note: I did try using []*Parameter instead of []Parameter, but it gave me the same result.
If you want an empty array, you have to provide an empty slice.
StatisticKey{
StatisticIdentifier: "id.string",
ParameterValueItems: []Parameter{},
}

How to Unmarshal the json array?

There is something wrong when I unmarshal the json array.
How do I correct it ? the code is:http://play.golang.org/p/AtU9q8Hlye
package main
import (
"encoding/json"
"fmt"
)
type Server struct {
ServerName string
ServerIP string
}
type Serverslice struct {
Name string
Servers []Server
}
func main() {
var s []Serverslice
str := `{"name":"dxh","servers":[{"serverName":"VPN0","serverIP":"127.0.0.1"},{"serverName":"Beijing_VPN","serverIP":"127.0.0.2"}],
"name":"dxh1,"servers":[{"serverName":"VPN1","serverIP":"127.0.0.1"},{"serverName":"Beijing_VPN","serverIP":"127.0.0.2"}]}`
json.Unmarshal([]byte(str), &s) //the wrong line.....................
fmt.Println(len(s))
}
First of all, you're ignoring the error return value from json.Unmarshal. You probably want something like:
if err := json.Unmarshal([]byte(str), &s); err != nil {
log.Fatalln(err)
}
With that change, we can see that your JSON data isn't valid: invalid character 's' after object key:value pair. There is a missing quote at the end of "dxh1 on the second line.
Fixing that error and rerunning the program you'll get a different error: json: cannot unmarshal object into Go value of type []main.Serverslice. There are two possible problems here:
You meant to decode into an object. In this case, just declare s as a Serverslice. Here is a version of your program that makes that change: http://play.golang.org/p/zgyr_vnn-_
Your JSON is supposed to be an array (possible, since it seems to have duplicate keys). Here's an updated version with the JSON changed to provide an array: http://play.golang.org/p/Wl6kUaivEm

Resources