如何使用反射设置切片接口的值

huangapple go评论67阅读模式
英文:

How to set slice interface values with reflection

问题

我想要构建一个函数,该函数接受一个通用指针数组,并根据MongoDB的结果填充该列表。

我不知道如何将从MongoDB获取的值设置到我的指针数组中。在下面的尝试中,程序会出现以下错误:reflect.Set: value of type []interface {} is not assignable to type []Person

当我打印出总数/找到的文档时,它与我期望的相符。所以我认为问题在于反射。

func getListWithCount(ctx context.Context, receiver interface{}) (int, error) {
	// 这里是我的MongoDB查询
	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)
}
英文:

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

答案1

得分: 2

动态创建一个与查询文档匹配的结构类型。有关详细信息,请参见下面的注释。

func getListWithCount(receiver interface{}) (int, error) {
	dst := reflect.ValueOf(receiver).Elem()

	// 在这里添加你的mongo查询

	// 创建一个与文档匹配的结构类型。
	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"`},
	})

	// 解码为该类型的值。
	docp := reflect.New(doct)
	if err := cursor.Decode(docp.Interface()); err != nil {
		return 0, err
	}
	docv := docp.Elem()

	// 将Documents字段复制到*receiver。
	dst.Set(docv.Field(1))

	// 返回总数
	return docv.Field(0).Interface().(int), 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
}

答案2

得分: 2

你可以将bson.RawValue解码为bson.RawValue,然后将其解码为receiver使用Unmarshal

func getListWithCount(ctx context.Context, receiver interface{}) (int, error) {
    // 这里是我的Mongo查询
    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 
}

你还可以将其实现为自定义的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)
}

使用这种方式,你可以在函数中这样使用:

func getListWithCount(ctx context.Context, receiver interface{}) (int, error) {
    // 这里是我的Mongo查询
    mongoResp := MongoResp{Documents: receiver}
    if err := cursor.Decode(&mongoResp); err != nil {
        return 0, err
    }
    return mongoResp.Total, nil 
}
英文:

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 
}

答案3

得分: -1

这里不需要使用reflect,你可以直接将其解码为你的Person切片。

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
}

len是结果的数量。

err = getPersons(ctx, coll, &persons)
require.NoError(t, err)
t.Logf("Got %d persons: %v", len(persons), persons)

请参考https://gist.github.com/xingyongtao/459f92490bdcbf7d5afe9f5d1ae6c04a。

英文:

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

huangapple
  • 本文由 发表于 2021年10月14日 21:00:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/69571189.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定