How to return a string via interface{} - go

I have the following function. I want to return the *string via the object interface{} parameter. If json.Unmarshal fails
I tried a bunch of variations but still it's coming out blank from the calling function. Although the type showing on the outside for the object is "string*", although it's empty. How can I do this?
My actual code below. But, for simplicity here an even simpler version.
https://go.dev/play/p/nnsKZxvU42M
// UnmarshalObject decodes an object from binary data
func UnmarshalObject(data []byte, object interface{}) error {
err := json.Unmarshal(data, object)
if err != nil {
s := string(data)
object = &s
}
return nil
}
It's being called like this
func (connection *DbConnection) GetObject(bucketName string, key []byte, object interface{}) error {
// ...
err = UnmarshalObject(data, object)
return err
}
From this function
// DBVersion retrieves the stored database version.
func (service *Service) DBVersion() (int, error) {
var version string
err := service.connection.GetObject(BucketName, []byte(versionKey), &version)
if err != nil {
return 0, err
}
return strconv.Atoi(version)
}
In this case, Atoi fails because version is ""

Generally, use a type-switch to check if the interface is any of your expected types.
func UnmarshalObject(data []byte, object interface{}) error {
err := json.Unmarshal(data, object)
if err {
return error;
}
switch object.(type) {
case string:
// object is a string
case int:
// object is an integer
default:
// object is something you did not expect.
return fmt.Errorf("Unknown type %T", v)
}
return nil;
}

I figured it out and learned something along the way. This form works.
// UnmarshalObject decodes an object from binary data
func UnmarshalObject(data []byte, object interface{}) error {
err := json.Unmarshal(data, object)
if err != nil {
if s, ok := object.(*string); !ok {
return err
}
*s = string(data)
}
return nil
}

Related

How can I return two different concrete types from a single method in Go 1.18?

Let say that I have this code:
type Type1 struct {
Name string `json:"name,omitempty"`
Path string `json:"path"`
File string `json:"file"`
Tag int `json:"tag"`
Num int `json:"num"`
}
func LoadConfiguration(data []byte) (*Type1, error) {
config, err := loadConf1(data)
if err != nil {
return nil, err
}
confOther, err := loadConfOther1()
if err != nil {
return nil, err
}
// do something with confOther
fmt.Println("confOther", confOther)
if confOther.Tag == 0 {
config.Num = 5
}
// do something with config attributes of type1
if config.Tag == 0 {
config.Tag = 5
}
if config.Num == 0 {
config.Num = 4
}
return config, nil
}
func loadConf1(bytes []byte) (*Type1, error) {
config := &Type1{}
if err := json.Unmarshal(bytes, config); err != nil {
return nil, fmt.Errorf("cannot load config: %v", err)
}
return config, nil
}
func loadConfOther1() (*Type1, error) {
// return value of this specific type
flatconfig := &Type1{}
// read a file as []byte
// written as a fixed array to simplify this example
fileContent := []byte{10, 22, 33, 44, 55}
if err := json.Unmarshal(fileContent, flatconfig); err != nil {
return nil, fmt.Errorf("cannot read config %v", err)
}
return flatconfig, nil
}
The only public function is LoadConfiguration.
It's based on a real code and It's used to read a json data as a specific struct. If something seems useless, it's because I simplified the original code.
The code above is ok, but now I want to create another struct type called "Type2" and re-use the same methods to read data into Type2 without copying and pasting everything.
type Type2 struct {
Name string `json:"name,omitempty"`
Path string `json:"path"`
Map *map[string]interface{} `json:"map"`
Other string `json:"other"`
}
Basically, I want to be able to call LoadConfiguration to get also Type2. I can accept to call a specific method like LoadConfiguration2, but I don't want to copy and paste also loadConf1 and loadConfOther1.
Is there a way to do that in an idiomatic way in Go 1.18?
Actually the code shown in your question doesn't do anything more than passing a type into json.Unmarshal and format an error so you can rewrite your function to behave just like it:
func LoadConfiguration(data []byte) (*Type1, error) {
config := &Type1{}
if err := loadConf(data, config); err != nil {
return nil, err
}
// ...
}
// "magically" accepts any type
// you could actually get rid of the intermediate function altogether
func loadConf(bytes []byte, config any) error {
if err := json.Unmarshal(bytes, config); err != nil {
return fmt.Errorf("cannot load config: %v", err)
}
return nil
}
In case the code actually does something more than just passing a pointer into json.Unmarshal, it can benefit from type parameters.
type Configurations interface {
Type1 | Type2
}
func loadConf[T Configurations](bytes []byte) (*T, error) {
config := new(T)
if err := json.Unmarshal(bytes, config); err != nil {
return nil, fmt.Errorf("cannot load config: %v", err)
}
return config, nil
}
func loadConfOther[T Configurations]() (*T, error) {
flatconfig := new(T)
// ... code
return flatconfig, nil
}
In these cases you can create a new pointer of either type with new(T) and then json.Unmarshal will take care of deserializing the content of the byte slice or file into it — provided the JSON can be actually unmarshalled into either struct.
The type-specific code in the top-level function should still be different, especially because you want to instantiate the generic functions with an explicit concrete type. So I advise to keep LoadConfiguration1 and LoadConfiguration2.
func LoadConfiguration1(data []byte) (*Type1, error) {
config, err := loadConf[Type1](data)
if err != nil {
return nil, err
}
confOther, err := loadConfOther[Type1]()
if err != nil {
return nil, err
}
// ... type specific code
return config, nil
}
However if the type-specific code is a small part of it, you can probably get away with a type-switch for the specific part, though it doesn't seem a viable option in your case. I would look like:
func LoadConfiguration[T Configuration](data []byte) (*T, error) {
config, err := loadConf[T](data)
if err != nil {
return nil, err
}
// let's pretend there's only one value of type parameter type
// type-specific code
switch t := config.(type) {
case *Type1:
// ... some *Type1 specific code
case *Type2:
// ... some *Type2 specific code
default:
// can't really happen because T is restricted to Configuration but helps catch errors if you extend the union and forget to add a corresponding case
panic("invalid type")
}
return config, nil
}
Minimal example playground: https://go.dev/play/p/-rhIgoxINTZ

Check if any variable conforms any interface using generics in Go

I am writing an API using go-fiber, and I want to check, if passed JSON conforms an interface that I want to see. So I decided to use 1.18's feature - generics. Here is what I did, but it does not work due to type problem.
func checkDataConformsInterface[I any](format I, c *fiber.Ctx) (I, error) {
if err := c.BodyParser(&format); err != nil {
return nil, err
}
return c.JSON(format), nil
}
The errors say
src/endpoints/v1/tasks.go:36:10: cannot use nil as I value in return statement
src/endpoints/v1/tasks.go:39:9: cannot use c.JSON(format) (value of type error) as type I in return statement
And I want to call the function like this:
type CreateTaskDF struct {
Target string `json:"target"`
Deepness int `json:"deepness"`
}
func CreateTask(c *fiber.Ctx) error {
data, err := checkDataConformsInterface[CreateTaskDF](&CreateTaskDF{}, c)
if err != nil {
log.Fatal(err)
}
// work with data here
...
How should I convert the return value in the function to make it work? Thanks!
It probably could work like this(if you do not consider any lib-based payload validators, which exist in almost every golang routing lib or web framework). So, to just validate your data you can use this:
func checkDataConformsInterface[I any](format I, c *fiber.Ctx) bool {
if err := c.BodyParser(&format); err != nil {
return false
}
return true
}
So I came up with the following solution
func checkDataConformsInterface[I any](format *I, c *fiber.Ctx) error {
if err := c.BodyParser(&format); err != nil {
return err
}
err := c.JSON(format)
if err != nil {
return err
}
return nil
}
which can be called like
func CreateTask(c *fiber.Ctx) error {
parsedData := CreateTaskDF{}
err := checkDataConformsInterface[CreateTaskDF](&parsedData, c)
if err != nil {
c.SendStatus(400)
return c.SendString("Wrong data")
}
Please, point me the problems if any

How to set slice interface values with reflection

I would like to build a function that takes a generic pointer array and fill that list based on mongo results.
I don't know how to set the value I got from mongo into my pointer array. In the below attempt, program panics with following error : reflect.Set: value of type []interface {} is not assignable to type []Person
When I print total / documents found, it corresponds to what I am expecting. So I think question is about reflection.
func getListWithCount(ctx context.Context, receiver interface{}) (int, error) {
//my mongo query here
var mongoResp struct {
Total int `bson:"total"`
Documents interface{} `bson:"documents"`
}
if err := cursor.Decode(&mongoResp); err != nil {
return 0, err
}
receiverValue := reflect.ValueOf(receiver)
docs := []interface{}(mongoResp.Documents.(primitive.A))
receiverValue.Elem().Set(reflect.ValueOf(docs))
return mongoResp.Total, nil
}
type Person struct {
Name string `bson:"name"`
}
func main() {
var persons []Person
count, err := getListWithCount(context.Background(), &persons)
if err != nil {
log.Fatal(err)
}
fmt.Println(count)
fmt.Println(persons)
}
You should be able to decode first into bson.RawValue and then Unmarshal it into the receiver.
func getListWithCount(ctx context.Context, receiver interface{}) (int, error) {
//my mongo query here
var mongoResp struct {
Total int `bson:"total"`
Documents bson.RawValue `bson:"documents"`
}
if err := cursor.Decode(&mongoResp); err != nil {
return 0, err
}
if err := mongoResp.Documents.Unmarshal(receiver); err != nil {
return 0, err
}
return mongoResp.Total, nil
}
You can also implement it as a custom bson.Unmarshaler.
type MongoResp struct {
Total int `bson:"total"`
Documents interface{} `bson:"documents"`
}
func (r *MongoResp) UnmarshalBSON(data []byte) error {
var temp struct {
Total int `bson:"total"`
Documents bson.RawValue `bson:"documents"`
}
if err := bson.Unmarshal(data, &temp); err != nil {
return err
}
r.Total = temp.Total
return temp.Documents.Unmarshal(r.Documents)
}
With that you would use it in the function like so:
func getListWithCount(ctx context.Context, receiver interface{}) (int, error) {
//my mongo query here
mongoResp := MongoResp{Documents: receiver}
if err := cursor.Decode(&mongoResp); err != nil {
return 0, err
}
return mongoResp.Total, nil
}
Dynamically create a struct type that matches the queried document. See commentary below for details.
func getListWithCount(receiver interface{}) (int, error) {
dst := reflect.ValueOf(receiver).Elem()
// Your mongo query here
// Create a struct type that matches the document.
doct := reflect.StructOf([]reflect.StructField{
reflect.StructField{Name: "Total", Type: reflect.TypeOf(0), Tag: `bson:"total"`},
reflect.StructField{Name: "Documents", Type: dst.Type(), Tag: `bson:"documents"`},
})
// Decode to a value of the type.
docp := reflect.New(doct)
if err := cursor.Decode(docp.Interface()); err != nil {
return 0, err
}
docv := docp.Elem()
// Copy the Documents field to *receiver.
dst.Set(docv.Field(1))
// Return the total
return docv.Field(0).Interface().(int), nil
}
there is no need to use reflect here, you can decode it directly to your Person slices
func getPersons(ctx context.Context, coll *mongo.Collection, results interface{}) error {
cur, err := coll.Find(ctx, bson.D{})
if err != nil {
return err
}
err = cur.All(ctx, results)
if err != nil {
return err
}
return nil
}
and the len is the count of the results.
err = getPersons(ctx, coll, &persons)
require.NoError(t, err)
t.Logf("Got %d persons: %v", len(persons), persons)
see https://gist.github.com/xingyongtao/459f92490bdcbf7d5afe9f5d1ae6c04a

How to judge unmarshal json interface{} type in golang?

I want to judge json type,but it always return "I don't know about type map[string]interface {}!",How to resolve it.
=========================================================================
type getRemoteCardInfo struct {
Code int
Msg string
Data []*remoteCardInfo
}
type remoteCardInfo struct {
Sn string
RemoteCardIp string
RemoteCardMac string
}
func Get_json_data(url string) (interface{}, error) {
client := &http.Client{}
req, err := http.NewRequest("GET", url, nil)
req.Header.Add("X-MYCMDB-Auth-Token", "sPf98SMBWzOZJEJB8KWltbJyKvFYPauu")
if err != nil {
return nil, err
}
resp, _ := client.Do(req)
if resp.StatusCode != http.StatusOK {
resp.Body.Close()
return nil, fmt.Errorf("query failed: %s", resp.Status)
}
var result interface{}
body, err := ioutil.ReadAll(resp.Body)
if err := json.Unmarshal(body, &result); err != nil {
log.Fatalf("JSON unmarshaling failed: %s", err)
}
resp.Body.Close()
fmt.Println(result)
return result, nil
}
func main() {
jsondata, err := Get_json_data(DHCPURL)
if err != nil {
log.Fatal(err)
}
switch v := jsondata.(type) {
case getRemoteCardInfo:
fmt.Println("aaaa")
default:
fmt.Printf("I don't know about type %T!\n", v)
}
The go JSON unmarshaler doesn't know about types, as you can tell by the fact that it stores the result into an interface{} value:
func Unmarshal(data []byte, v interface{}) error
// "v" can be any type -------^
So it's up to you to use the unmarshaler to populate your structure and determine if the result is valid or not.
In your example it looks like you're trying to unmarshal a remoteCardInfo from an HTTP response. To do this you should unmarshal into an empty remoteCardInfo struct and determine if the required fields were populated.
For example, suppose you expect a JSON document like so:
{
"sn": "123",
"ip": "0.0.0.0",
"mac": "ff:ff:ff:ff:ff:ff"
}
Then you should define your "remoteCardInfo" struct as below:
type remoteCardInfo struct {
Sn string `json:"sn"`
RemoteCardIp string `json:"ip"`
RemoteCardMac string `json:"mac"`
}
And then unmarshal and validate it like so:
func getRemoteCardInfo(bs []byte) (*remoteCardInfo, error) {
rci := remoteCardInfo{}
err := json.Unmarshal(bs, &rci)
if err != nil {
return nil, err
}
// Validate the expected fields
if rci.Sn == "" {
return nil, fmt.Errorf(`missing "sn"`)
}
if rci.RemoteCardIp == "" {
return nil, fmt.Errorf(`missing "ip"`)
}
if rci.RemoteCardMac == "" {
return nil, fmt.Errorf(`missing "mac"`)
}
return &rci, nil
}
Of course, you can validate the fields any way you like but the main thing to remember is that the unmarshaler only does the job of ensuring that the input byte array is a valid JSON document and populates the fields from the document into the fields defined by the value.
It cannot tell you what "type" of object the JSON document represents.

Custom json unmarshaler return empty fields

I've implemented a custom JSON unmarshaler, but for some reason it won't return the proper value -all fields come back nil.
For example:
type test struct {
t string
}
func New(data string) (*test, error) {
return &test{t: data}, nil
}
func (t *test) UnmarshalJSON(b []byte) error {
tt, err := New(string(b))
if err != nil {
return err
}
t = tt
return nil
}
func main() {
str := `"hello"`
b := []byte(str)
t := &test{}
err := json.Unmarshal(b, t)
if err != nil {
fmt.Printf("unmarshal error occurred: %#v", err)
}
fmt.Printf("%#v", t)
}
https://play.golang.org/p/LuXkZQZHWz
The above code shows the output: &main.test{t:""}
Why doesn't it unmarshal the fields? i.e &main.test{t:"hello"}
Only when I dereference the pointers above, do I get the desired result.
i.e -
func (t *test) UnmarshalJSON(b []byte) error {
tt, err := New(string(b))
if err != nil {
return err
}
*t = *tt
return nil
}
You're assigning the local variable t, a pointer to test, to the value of the local variable tt, also a pointer to test. This has no effect on the value the original pointer t pointed to. You have to dereference the pointers to change the value it points to, rather than changing the local pointer itself:
*t = *tt

Resources