从结构中删除字段或在JSON响应中隐藏它们

我在Go中创建了一个API,在被调用时,执行查询,创建一个结构实例,然后在发送回调用者之前将该结构编码为JSON。我现在想允许调用者能够通过传递一个“;fields"”来选择他们希望返回的特定字段。得到参数。

这意味着根据字段的值,我的结构将会改变。是否有方法从结构中删除字段?或者至少动态地将它们隐藏在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)
240389 次浏览

问题是要求根据调用者提供的字段列表选择字段为动态。这是不可能用静态定义的json struct标记完成的。

如果你想要的是总是跳过一个字段到json编码,那么当然使用json:"-"来忽略该字段。(还请注意,如果字段未导出,则需要;这些字段总是被json编码器忽略。)这不是问题要问的。

引用json:"-"答案的注释:

这个[json:"-"答案]是大多数人在搜索结束后想要的答案,但它不是问题的答案。

在这种情况下,我将使用map[string]interface{}而不是结构体。你可以通过调用映射中要删除的字段的内置delete来轻松删除字段。

也就是说,如果您不能首先查询所请求的字段。

你可以使用标签属性“omitifempty”或可选字段指针,并留下那些你想跳过未初始化。

你可以使用reflect包通过反射字段标记和选择json标记值来选择你想要的字段。在SearchResults类型上定义一个方法,该方法选择你想要的字段,并将它们作为map[string]interface{}返回,然后封送而不是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

另一种方法是使用带有,omitempty标记的指针结构体。如果指针是,字段将不会被封送。

这种方法不需要额外的反射或低效地使用地图。

与jorelli使用此方法的示例相同:http://play.golang.org/p/JJNa0m2_nw

使用json:“- - -”

// Field is ignored by this package.
Field int `json:"-"`


// Field appears in JSON as key "myName".
Field int `json:"myName"`


// Field appears in JSON as key "myName" and
// the field is omitted from the object if its value is empty,
// as defined above.
Field int `json:"myName,omitempty"`


// Field appears in JSON as key "Field" (the default), but
// the field is skipped if empty.
// Note the leading comma.
Field int `json:",omitempty"`

doc: http://golang.org/pkg/encoding/json/#Marshal

这个问题现在有点老了,但我不久前遇到了同样的问题,因为我发现没有简单的方法来做到这一点,我建立了一个库来实现这个目的。 它允许轻松地从静态结构体生成map[string]interface{}

https://github.com/tuvistavie/structomap

以三种成分为例:

  1. reflect包循环遍历结构的所有字段。

  2. 一个if语句来获取你想要Marshal的字段,和

  3. encoding/json包到Marshal你喜欢的字段。

准备:

  1. 把它们按适当的比例混合。使用reflect.TypeOf(your_struct).Field(i).Name()获取your_structith字段的名称。

  2. 使用reflect.ValueOf(your_struct).Field(i)来获得your_structith字段的类型Value表示。

  3. 使用fieldValue.Interface()检索类型为ValuefieldValue的实际值(转换为类型interface{})(注意括号使用- interface () 方法产生interface{}

如果你幸运地设法不烧毁任何晶体管或断路器在这个过程中,你应该得到这样的东西:

func MarshalOnlyFields(structa interface{},
includeFields map[string]bool) (jsona []byte, status error) {
value := reflect.ValueOf(structa)
typa := reflect.TypeOf(structa)
size := value.NumField()
jsona = append(jsona, '{')
for i := 0; i < size; i++ {
structValue := value.Field(i)
var fieldName string = typa.Field(i).Name
if marshalledField, marshalStatus := json.Marshal((structValue).Interface()); marshalStatus != nil {
return []byte{}, marshalStatus
} else {
if includeFields[fieldName] {
jsona = append(jsona, '"')
jsona = append(jsona, []byte(fieldName)...)
jsona = append(jsona, '"')
jsona = append(jsona, ':')
jsona = append(jsona, (marshalledField)...)
if i+1 != len(includeFields) {
jsona = append(jsona, ',')
}
}
}
}
jsona = append(jsona, '}')
return
}

服务:

例如,使用任意结构体和你想包含的字段的map[string]bool来服务

type magic struct {
Magic1 int
Magic2 string
Magic3 [2]int
}


func main() {
var magic = magic{0, "tusia", [2]int{0, 1}}
if json, status := MarshalOnlyFields(magic, map[string]bool{"Magic1": true}); status != nil {
println("error")
} else {
fmt.Println(string(json))
}


}

祝你胃口好!

我刚刚发布了治安官,它根据结构字段上标注的标记将结构转换为映射。然后可以编组(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)
}

我没有同样的问题,而是类似的问题。下面的代码也解决了您的问题,当然,如果您不介意性能问题。在将这种解决方案应用到您的系统之前,我建议您如果可以的话重新设计您的结构。发送可变结构响应是过度工程。我认为响应结构代表了请求和资源之间的契约,它不应该依赖于请求。(你可以把不想要的字段设为null,我就是这么做的)。在某些情况下,我们必须实现这种设计,如果你相信你在这种情况下,这里是播放链接和代码我使用。

type User2 struct {
ID       int    `groups:"id" json:"id,omitempty"`
Username string `groups:"username" json:"username,omitempty"`
Nickname string `groups:"nickname" json:"nickname,omitempty"`
}


type User struct {
ID       int    `groups:"private,public" json:"id,omitempty"`
Username string `groups:"private" json:"username,omitempty"`
Nickname string `groups:"public" json:"nickname,omitempty"`
}


var (
tagName = "groups"
)


//OmitFields sets fields nil by checking their tag group value and access control tags(acTags)
func OmitFields(obj interface{}, acTags []string) {
//nilV := reflect.Value{}
sv := reflect.ValueOf(obj).Elem()
st := sv.Type()
if sv.Kind() == reflect.Struct {
for i := 0; i < st.NumField(); i++ {
fieldVal := sv.Field(i)
if fieldVal.CanSet() {
tagStr := st.Field(i).Tag.Get(tagName)
if len(tagStr) == 0 {
continue
}
tagList := strings.Split(strings.Replace(tagStr, " ", "", -1), ",")
//fmt.Println(tagList)
// ContainsCommonItem checks whether there is at least one common item in arrays
if !ContainsCommonItem(tagList, acTags) {
fieldVal.Set(reflect.Zero(fieldVal.Type()))
}
}
}
}
}


//ContainsCommonItem checks if arrays have at least one equal item
func ContainsCommonItem(arr1 []string, arr2 []string) bool {
for i := 0; i < len(arr1); i++ {
for j := 0; j < len(arr2); j++ {
if arr1[i] == arr2[j] {
return true
}
}
}
return false
}
func main() {
u := User{ID: 1, Username: "very secret", Nickname: "hinzir"}
//assume authenticated user doesn't has permission to access private fields
OmitFields(&u, []string{"public"})
bytes, _ := json.Marshal(&u)
fmt.Println(string(bytes))




u2 := User2{ID: 1, Username: "very secret", Nickname: "hinzir"}
//you want to filter fields by field names
OmitFields(&u2, []string{"id", "nickname"})
bytes, _ = json.Marshal(&u2)
fmt.Println(string(bytes))


}

我也面临着这个问题,起初我只是想在我的http处理程序中专门化响应。我的第一种方法是创建一个包,将一个结构体的信息复制到另一个结构体,然后封送第二个结构体。我用反射来做那个包,我不喜欢那种方法,我也不是动态的。

所以我决定修改encoding/json包来做到这一点。函数MarshalMarshalIndent(Encoder) Encode额外接收一个

type F map[string]F

我想模拟需要封送的字段的JSON,因此它只封送映射中的字段。

https://github.com/jtorz/jsont

package main


import (
"fmt"
"log"
"net/http"


"github.com/jtorz/jsont/v2"
)


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
func main() {
msg := SearchResults{
NumberResults: 2,
Results: []SearchResult{
{
Date:        "12-12-12",
IdCompany:   1,
Company:     "alfa",
IdIndustry:  1,
Industry:    "IT",
IdContinent: 1,
Continent:   "america",
IdCountry:   1,
Country:     "México",
IdState:     1,
State:       "CDMX",
IdCity:      1,
City:        "Atz",
},
{
Date:        "12-12-12",
IdCompany:   2,
Company:     "beta",
IdIndustry:  1,
Industry:    "IT",
IdContinent: 1,
Continent:   "america",
IdCountry:   2,
Country:     "USA",
IdState:     2,
State:       "TX",
IdCity:      2,
City:        "XYZ",
},
},
}
fmt.Println(msg)
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {


//{"numberResults":2,"results":[{"date":"12-12-12","idCompany":1,"idIndustry":1,"country":"México"},{"date":"12-12-12","idCompany":2,"idIndustry":1,"country":"USA"}]}
err := jsont.NewEncoder(w).Encode(msg, jsont.F{
"numberResults": nil,
"results": jsont.F{
"date":       nil,
"idCompany":  nil,
"idIndustry": nil,
"country":    nil,
},
})
if err != nil {
log.Fatal(err)
}
})


http.ListenAndServe(":3009", nil)
}

我创建了这个函数,通过忽略一些字段将struct转换为JSON字符串。希望能有所帮助。

func GetJSONString(obj interface{}, ignoreFields ...string) (string, error) {
toJson, err := json.Marshal(obj)
if err != nil {
return "", err
}


if len(ignoreFields) == 0 {
return string(toJson), nil
}


toMap := map[string]interface{}{}
json.Unmarshal([]byte(string(toJson)), &toMap)


for _, field := range ignoreFields {
delete(toMap, field)
}


toJson, err = json.Marshal(toMap)
if err != nil {
return "", err
}
return string(toJson), nil
}

例如:https://play.golang.org/p/nmq7MFF47Gp

下面是我如何定义我的结构。

type User struct {
Username string  `json:"username" bson:"username"`
Email    string  `json:"email" bson:"email"`
Password *string `json:"password,omitempty" bson:"password"`
FullName string  `json:"fullname" bson:"fullname"`
}

在我的函数集user.Password = nil中,不被封送。