我正在尝试根据查询参数实现休息资源上的属性选择。 API客户端将提供一个名为
fields
的查询参数。服务器将仅返回查询字符串中提到的资源的属性。服务器应根据查询参数返回资源的不同部分表示。以下是一些请求示例。
GET /api/person/42/?fields=id,createdAt
GET /api/person/42/?fields=address,account
GET /api/person/42/?fields=id,priority,address.city
我尝试走
map[string]any
路线,但进展并不顺利。我正在使用 MongoDB。当我将 mongo 文档解码为 map[string]any
时,字段名称和类型不匹配。因此我试图动态创建一个新的结构。
这是我的尝试:
func main() {
query, _ := url.ParseQuery("fields=id,priority,address.city")
fields := strings.Split(query.Get("fields"), ",") // TODO: extractFields
person := getPerson() // Returns a Person Struct
personish := PartialStruct(person, fields)
marshalled, _ := json.Marshal(personish) // TODO: err
fmt.Println(string(marshalled))
}
func PartialStruct(original any, fields []string) any {
// Is there any alternative to reflect ?
originalType := reflect.TypeOf(original)
partialFields := make([]reflect.StructField, 0)
for _, field := range reflect.VisibleFields(originalType) {
queryName := field.Tag.Get("json") // TODO: extractQueryName
if slices.Contains(fields, queryName) {
partialFields = append(partialFields, field)
}
}
partialType := reflect.StructOf(partialFields)
// Is there any alternative to Marshal/Unmarshal?
partial := reflect.New(partialType).Interface()
marshalled, _ := json.Marshal(original) // TODO: err
_ = json.Unmarshal(marshalled, partial) // TODO: err
return partial
}
这是一个可运行的示例https://go.dev/play/p/Egomxe5NjEc
资源被建模为嵌套结构。嵌套字段将用“.”表示。查询字符串中的点。
如何改进
PartialStruct
来处理 address.city
等嵌套字段?
如果有更好的方法我愿意改变方向。
查看第三方库:graphql
我写的一个例子可能对你有帮助:
package main
import (
"encoding/json"
"fmt"
"log"
"github.com/graphql-go/graphql"
)
func main() {
// Schema
fields := graphql.Fields{
"id": &graphql.Field{
Type: graphql.ID,
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
return 111, nil
},
},
"priority": &graphql.Field{
Type: graphql.String,
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
return "admin", nil
},
},
"address": &graphql.Field{
Type: graphql.NewObject(graphql.ObjectConfig{
Name: "address",
Fields: graphql.Fields{
"city": &graphql.Field{
Type: graphql.String,
},
"country": &graphql.Field{
Type: graphql.String,
},
},
}),
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
return map[string]string{
"city":"New York",
"country": "us",
}, nil
},
},
}
rootQuery := graphql.ObjectConfig{Name: "RootQuery", Fields: fields}
schemaConfig := graphql.SchemaConfig{Query: graphql.NewObject(rootQuery)}
schema, err := graphql.NewSchema(schemaConfig)
if err != nil {
log.Fatalf("failed to create new schema, error: %v", err)
}
// Query
query := `
{
id,
address {
city,country
},
priority
}
`
params := graphql.Params{Schema: schema, RequestString: query}
r := graphql.Do(params)
if len(r.Errors) > 0 {
log.Fatalf("failed to execute graphql operation, errors: %+v", r.Errors)
}
rJSON, _ := json.Marshal(r)
fmt.Printf("%s \n", rJSON)
}
这是一个可运行的示例 https://go.dev/play/p/pHH2iBzCLT-
您正在寻找的库是 json-mask-go 它将处理编组后的部分响应
这是一个示例(另请注意将
address.city
更改为 address/city
)
https://go.dev/play/p/IelEOjctZ5t
query, _ := url.ParseQuery("fields=id,priority,address/city")
person := getPerson()
marshalled, _ := json.Marshal(person) // TODO: err
personish, err := jsonmask.Mask([]byte(marshalled), query.Get("fields"))
if err != nil {
fmt.Println("Error", err)
return
}
fmt.Println(string(personish))