Home > Back-end >  How to set slice interface values with reflection
How to set slice interface values with reflection

Time:10-16

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)
}

CodePudding user response:

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
}

CodePudding user response:

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 
}

CodePudding user response:

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

  •  Tags:  
  • go
  • Related