Custom json unmarshaler return empty fields - go

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

Related

Go: Implementing a ManyDecode for a "set" of individual results

I have implemented a very simple Decode method (using gob.Decoder for now) - this works well for single responses - it would even work well for slices, but I need to implement a DecodeMany method where it is able to decode a set of individual responses (not a slice).
Working Decode method:
var v MyType
_ = Decode(&v)
...
func Decode(v interface{}) error {
buf, _ := DoSomething() // func DoSomething() ([]byte, error)
// error handling omitted for brevity
return gob.NewDecoder(bytes.NewReader(buf)).Decode(v)
}
What I'm trying to do for a DecodeMany method is to deal with a response that isn't necessarily a slice:
var vv []MyType
_ = DecodeMany(&vv)
...
func DecodeMany(vv []interface{}) error {
for _, g := range DoSomething() { // func DoSomething() []struct{Buf []bytes}
// Use g.Buf as an individual "interface{}"
// want something like:
var v interface{} /* Somehow create instance of single vv type? */
_ = gob.NewDecoder(bytes.NewReader(g.Buf)).Decode(v)
vv = append(vv, v)
}
return
}
Besides not compiling the above also has the error of:
cannot use &vv (value of type *[]MyType) as type []interface{} in argument to DecodeMany
If you want to modify the passed slice, it must be a pointer, else you must return a new slice. Also if the function is declared to have a param of type []interface{}, you can only pass a value of type []interface{} and no other slice types... Unless you use generics...
This is a perfect example to start using generics introduced in Go 1.18.
Change DecodeMany() to be generic, having a T type parameter being the slice element type:
When taking a pointer
func DecodeMany[T any](vv *[]T) error {
for _, g := range DoSomething() {
var v T
if err := gob.NewDecoder(bytes.NewReader(g.Buf)).Decode(&v); err != nil {
return err
}
*vv = append(*vv, v)
}
return nil
}
Here's a simple app to test it:
type MyType struct {
S int64
}
func main() {
var vv []MyType
if err := DecodeMany(&vv); err != nil {
panic(err)
}
fmt.Println(vv)
}
func DoSomething() (result []struct{ Buf []byte }) {
for i := 3; i < 6; i++ {
buf := &bytes.Buffer{}
v := MyType{S: int64(i)}
if err := gob.NewEncoder(buf).Encode(v); err != nil {
panic(err)
}
result = append(result, struct{ Buf []byte }{buf.Bytes()})
}
return
}
This outputs (try it on the Go Playground):
[{3} {4} {5}]
When returning a slice
If you choose to return the slice, you don't have to pass anything, but you need to assign the result:
func DecodeMany[T any]() ([]T, error) {
var result []T
for _, g := range DoSomething() {
var v T
if err := gob.NewDecoder(bytes.NewReader(g.Buf)).Decode(&v); err != nil {
return result, err
}
result = append(result, v)
}
return result, nil
}
Using it:
vv, err := DecodeMany[MyType]()
if err != nil {
panic(err)
}
fmt.Println(vv)
Try this one on the Go Playground.

How to return a string via interface{}

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
}

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

Custom unmarshall to a type than input out different type

I'm learning Go and I have a question:
I have an Info type that is defined like that:
type Info struct {
ID ID `json:"id,omitempty"`
DisplayName string `json:"display_name,omitempty"`
}
I made a custom UnmarshallJSON function to unmarshall this struct because as an input I have either:
An []interface{} with at position [0] an int and [1] a string
A boolean always equals to false meaning that the field is null
I want that when the input is false, the Info is nil.
Here's the UnmarshallJSON function
func (i *Info) UnmarshalJSON(data []byte) error {
var v []interface{}
if err := json.Unmarshal(data, &v); err != nil {
var v bool
if err = json.Unmarshal(data, &v); err != nil {
return err
}
return nil
}
i.ID = ID(v[0].(float64))
i.DisplayName = v[1].(string)
return nil
}
It's ugly, and I would like to know if there's a better option.
Thank you very much.
Fist you should be more defensive about unexpected types and length to avoid a panic. Then you can unmarshal into a []json.RawMessage to defer unmarshaling of the elements until you are ready. Finally you should guard against your invalid true.
Here is my best effort, please others feel free to edit (here is a playground):
func (i *Info) UnmarshalJSON(data []byte) error {
var raw interface{}
if err := json.Unmarshal(data, &raw); err != nil {
return err
}
switch r := raw.(type) {
case []interface{}:
case bool:
if r {
return errors.New("unexpected true, must be array or false")
}
return nil
default:
return fmt.Errorf("unexpected type %T, must be array or false", r)
}
var v []json.RawMessage
if err := json.Unmarshal(data, &v); err != nil {
return err
}
if len(v) != 2 {
return fmt.Errorf("unexpected length %d, must be 2", len(v))
}
if err := json.Unmarshal(v[0], &i.ID); err != nil {
return err
}
if err := json.Unmarshal(v[1], &i.DisplayName); err != nil {
return err
}
return nil
}

multiple-value in single-value context no return func

I have a func in Go that simply writes to a buffer. I have no return type set on the func so I am not sure why I am seeing this error. Here is my code:
func Write(buffer *bytes.Buffer, values ...string) {
for _, val := range values
_, err := *buffer.WriteString(val)
if err != nil {
// print error
}
}
_, err := *buffer.WriteString(" ")
if err != nil {
// print error
}
}
It complains at both lines where I have buffer.WriteString. This leads me to believe it has something to do with the return types of the WriteString method on the buffer but I am not experienced enough in Go to know for sure.
Any help would be appreciated.
Edit: Updated code.
You don't need to dereference pointers to call methods in Go. The * operator before buffer.WriteString is applied to the returned values. To dereference buffer you would need to write (*buffer).WriteString, but that's not needed at all:
func Write(buffer *bytes.Buffer, values ...string) {
for _, val := range values {
_, err := buffer.WriteString(val)
if err != nil {
// print error
}
}
_, err := buffer.WriteString(" ")
if err != nil {
// print error
}
}

Resources