我在Go中创建了一个API,在被调用时,执行查询,创建一个结构实例,然后在发送回调用者之前将该结构编码为JSON。现在,我想允许调用者通过传入“fields”GET参数来选择他们想要返回的特定字段。
这意味着根据字段的值,我的结构将会改变。是否有方法从结构中删除字段?或者至少动态地将它们隐藏在JSON响应中?(注:有时我有空值,所以JSON omitEmpty标签将不在这里工作)如果这些都不可能,有没有一个更好的方法来处理这个问题的建议?
下面是我正在使用的结构体的一个较小版本:
type SearchResult struct {
Date string `json:"date"`
IdCompany int `json:"idCompany"`
Company string `json:"company"`
IdIndustry interface{} `json:"idIndustry"`
Industry string `json:"industry"`
IdContinent interface{} `json:"idContinent"`
Continent string `json:"continent"`
IdCountry interface{} `json:"idCountry"`
Country string `json:"country"`
IdState interface{} `json:"idState"`
State string `json:"state"`
IdCity interface{} `json:"idCity"`
City string `json:"city"`
} //SearchResult
type SearchResults struct {
NumberResults int `json:"numberResults"`
Results []SearchResult `json:"results"`
} //type SearchResults
然后我编码并输出响应,就像这样:
err := json.NewEncoder(c.ResponseWriter).Encode(&msg)
您可以使用反射包通过反射字段标记和选择json标记值来选择您想要的字段。在SearchResults类型上定义一个方法,该方法选择您想要的字段,并将它们作为映射[string]接口{}返回,然后封送该方法而不是SearchResults结构本身。下面是如何定义该方法的示例:
func fieldSet(fields ...string) map[string]bool {
set := make(map[string]bool, len(fields))
for _, s := range fields {
set[s] = true
}
return set
}
func (s *SearchResult) SelectFields(fields ...string) map[string]interface{} {
fs := fieldSet(fields...)
rt, rv := reflect.TypeOf(*s), reflect.ValueOf(*s)
out := make(map[string]interface{}, rt.NumField())
for i := 0; i < rt.NumField(); i++ {
field := rt.Field(i)
jsonKey := field.Tag.Get("json")
if fs[jsonKey] {
out[jsonKey] = rv.Field(i).Interface()
}
}
return out
}
这里有一个可运行的解决方案,展示了如何调用这个方法并编组您的选择:http://play.golang.org/p/1K9xjQRnO8
我刚刚发布了sheriff,它将结构转换为基于结构字段上标注的标记的映射。然后可以编组(JSON或其他)生成的映射。它可能不允许您只序列化调用者请求的字段集,但我认为使用组集将允许您覆盖大多数情况。使用组而不是直接使用字段也很可能提高缓存能力。
例子:
package main
import (
"encoding/json"
"fmt"
"log"
"github.com/hashicorp/go-version"
"github.com/liip/sheriff"
)
type User struct {
Username string `json:"username" groups:"api"`
Email string `json:"email" groups:"personal"`
Name string `json:"name" groups:"api"`
Roles []string `json:"roles" groups:"api" since:"2"`
}
func main() {
user := User{
Username: "alice",
Email: "alice@example.org",
Name: "Alice",
Roles: []string{"user", "admin"},
}
v2, err := version.NewVersion("2.0.0")
if err != nil {
log.Panic(err)
}
o := &sheriff.Options{
Groups: []string{"api"},
ApiVersion: v2,
}
data, err := sheriff.Marshal(o, user)
if err != nil {
log.Panic(err)
}
output, err := json.MarshalIndent(data, "", " ")
if err != nil {
log.Panic(err)
}
fmt.Printf("%s", output)
}
您可以使用反射包通过反射字段标记和选择json标记值来选择您想要的字段。在SearchResults类型上定义一个方法,该方法选择您想要的字段,并将它们作为映射[string]接口{}返回,然后封送该方法而不是SearchResults结构本身。下面是如何定义该方法的示例:
func fieldSet(fields ...string) map[string]bool {
set := make(map[string]bool, len(fields))
for _, s := range fields {
set[s] = true
}
return set
}
func (s *SearchResult) SelectFields(fields ...string) map[string]interface{} {
fs := fieldSet(fields...)
rt, rv := reflect.TypeOf(*s), reflect.ValueOf(*s)
out := make(map[string]interface{}, rt.NumField())
for i := 0; i < rt.NumField(); i++ {
field := rt.Field(i)
jsonKey := field.Tag.Get("json")
if fs[jsonKey] {
out[jsonKey] = rv.Field(i).Interface()
}
}
return out
}
这里有一个可运行的解决方案,展示了如何调用这个方法并编组您的选择:http://play.golang.org/p/1K9xjQRnO8