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

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

Removing fields from struct or hiding them in JSON Response

问题

我在Go中创建了一个API,当被调用时,执行一个查询,创建一个结构的实例,然后将该结构编码为JSON并发送回调用者。现在,我想允许调用者通过传递一个"fields" GET参数来选择他们想要返回的特定字段。

这意味着根据字段的值,我的结构会发生变化。有没有办法从结构中删除字段?或者至少在JSON响应中动态隐藏它们?(注意:有时我有空值,所以JSON omitEmpty标签在这里不起作用)如果这两个都不可能,是否有更好的处理方式的建议?

我使用的结构的较小版本如下:

  1. type SearchResult struct {
  2. Date string `json:"date"`
  3. IdCompany int `json:"idCompany"`
  4. Company string `json:"company"`
  5. IdIndustry interface{} `json:"idIndustry"`
  6. Industry string `json:"industry"`
  7. IdContinent interface{} `json:"idContinent"`
  8. Continent string `json:"continent"`
  9. IdCountry interface{} `json:"idCountry"`
  10. Country string `json:"country"`
  11. IdState interface{} `json:"idState"`
  12. State string `json:"state"`
  13. IdCity interface{} `json:"idCity"`
  14. City string `json:"city"`
  15. } //SearchResult
  16. type SearchResults struct {
  17. NumberResults int `json:"numberResults"`
  18. Results []SearchResult `json:"results"`
  19. } //type SearchResults

然后像这样编码和输出响应:

  1. err := json.NewEncoder(c.ResponseWriter).Encode(&msg)
英文:

I've created an API in Go that, upon being called, performs a query, creates an instance of a struct, and then encodes that struct as JSON before sending back to the caller. I'd now like to allow the caller to be able to select the specific fields they would like returned by passing in a "fields" GET parameter.

This means depending on the fields value(s), my struct would change. Is there any way to remove fields from a struct? Or at least hide them in the JSON response dynamically? (Note: Sometimes I have empty values so the JSON omitEmpty tag will not work here) If neither of these are possible, is there a suggestion on a better way to handle this?

A smaller version of the structs I'm using are below:

  1. type SearchResult struct {
  2. Date string `json:"date"`
  3. IdCompany int `json:"idCompany"`
  4. Company string `json:"company"`
  5. IdIndustry interface{} `json:"idIndustry"`
  6. Industry string `json:"industry"`
  7. IdContinent interface{} `json:"idContinent"`
  8. Continent string `json:"continent"`
  9. IdCountry interface{} `json:"idCountry"`
  10. Country string `json:"country"`
  11. IdState interface{} `json:"idState"`
  12. State string `json:"state"`
  13. IdCity interface{} `json:"idCity"`
  14. City string `json:"city"`
  15. } //SearchResult
  16. type SearchResults struct {
  17. NumberResults int `json:"numberResults"`
  18. Results []SearchResult `json:"results"`
  19. } //type SearchResults

I then encode and output the response like so:

  1. err := json.NewEncoder(c.ResponseWriter).Encode(&msg)

答案1

得分: 450

问题是要求根据调用者提供的字段列表动态选择字段。这是不可能通过静态定义的json结构标签来实现的。

如果你想要始终跳过一个字段进行json编码,那么当然可以使用json:"-"来忽略该字段。(请注意,如果字段未导出,则json编码器始终会忽略这些字段,因此不需要使用json:"-"。)但这不是问题所问的。

引用json:"-"答案的评论:

> 这个(json:"-答案)是大多数通过搜索到达这里的人想要的答案,但它不是问题的答案。

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

也就是说,如果你不能一开始就只查询所请求的字段。

英文:

The question is asking for fields to be dynamically selected based on the caller-provided list of fields. This isn't possible to be done with the statically-defined json struct tag.

If what you want is to always skip a field to json-encode, then of course use json:"-" to ignore the field. (Note also that this is not required if your field is unexported; those fields are always ignored by the json encoder.) This isn't what the question asks.

To quote the comment on the json:"-" answer:

> This [the json:"-" answer] is the answer most people ending up here from searching would want, but it's not the answer to the question.

I'd use a map[string]interface{} instead of a struct in this case. You can easily remove fields by calling the delete built-in on the map for the fields to remove.

That is, if you can't query only for the requested fields in the first place.

答案2

得分: 234

使用 `json:"-"`

  1. // 该字段被此包忽略。
  2. Field int `json:"-"`
  3. // 该字段在JSON中以键"myName"出现。
  4. Field int `json:"myName"`
  5. // 该字段在JSON中以键"myName"出现,并且如果其值为空,则从对象中省略该字段,如上所定义。
  6. Field int `json:"myName,omitempty"`
  7. // 该字段在JSON中以键"Field"(默认)出现,但如果为空,则跳过该字段。
  8. // 注意前导逗号。
  9. Field int `json:",omitempty"`

文档:http://golang.org/pkg/encoding/json/#Marshal

英文:

use `json:"-"`

  1. // Field is ignored by this package.
  2. Field int `json:"-"`
  3. // Field appears in JSON as key "myName".
  4. Field int `json:"myName"`
  5. // Field appears in JSON as key "myName" and
  6. // the field is omitted from the object if its value is empty,
  7. // as defined above.
  8. Field int `json:"myName,omitempty"`
  9. // Field appears in JSON as key "Field" (the default), but
  10. // the field is skipped if empty.
  11. // Note the leading comma.
  12. Field int `json:",omitempty"`

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

答案3

得分: 80

另一种方法是使用带有,omitempty标签的指针结构体。如果指针为nil,则字段将不会被编组。

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

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

英文:

Another way to do this is to have a struct of pointers with the ,omitempty tag. If the pointers are nil, the fields won't be Marshalled.

This method will not require additional reflection or inefficient use of maps.

Same example as jorelli using this method: http://play.golang.org/p/JJNa0m2_nw

答案4

得分: 19

你可以使用reflect包来通过反射字段标签并选择json标签值来选择你想要的字段。在你的SearchResults类型上定义一个方法,该方法选择你想要的字段并将它们作为map[string]interface{}返回,然后将对象编组而不是SearchResults结构本身。以下是如何定义该方法的示例:

  1. func fieldSet(fields ...string) map[string]bool {
  2. set := make(map[string]bool, len(fields))
  3. for _, s := range fields {
  4. set
    展开收缩
    = true
  5. }
  6. return set
  7. }
  8. func (s *SearchResult) SelectFields(fields ...string) map[string]interface{} {
  9. fs := fieldSet(fields...)
  10. rt, rv := reflect.TypeOf(*s), reflect.ValueOf(*s)
  11. out := make(map[string]interface{}, rt.NumField())
  12. for i := 0; i < rt.NumField(); i++ {
  13. field := rt.Field(i)
  14. jsonKey := field.Tag.Get("json")
  15. if fs[jsonKey] {
  16. out[jsonKey] = rv.Field(i).Interface()
  17. }
  18. }
  19. return out
  20. }

这是一个可运行的解决方案,展示了如何调用这个方法并编组你的选择:http://play.golang.org/p/1K9xjQRnO8

英文:

You can use the reflect package to select the fields that you want by reflecting on the field tags and selecting the json tag values. Define a method on your SearchResults type that selects the fields you want and returns them as a map[string]interface{}, and then marshal that instead of the SearchResults struct itself. Here's an example of how you might define that method:

  1. func fieldSet(fields ...string) map[string]bool {
  2. set := make(map[string]bool, len(fields))
  3. for _, s := range fields {
  4. set
    展开收缩
    = true
  5. }
  6. return set
  7. }
  8. func (s *SearchResult) SelectFields(fields ...string) map[string]interface{} {
  9. fs := fieldSet(fields...)
  10. rt, rv := reflect.TypeOf(*s), reflect.ValueOf(*s)
  11. out := make(map[string]interface{}, rt.NumField())
  12. for i := 0; i &lt; rt.NumField(); i++ {
  13. field := rt.Field(i)
  14. jsonKey := field.Tag.Get(&quot;json&quot;)
  15. if fs[jsonKey] {
  16. out[jsonKey] = rv.Field(i).Interface()
  17. }
  18. }
  19. return out
  20. }

and here's a runnable solution that shows how you would call this method and marshal your selection: http://play.golang.org/p/1K9xjQRnO8

答案5

得分: 9

使用三个组件:

  1. reflect 包来遍历结构体的所有字段。

  2. if 语句来选择要 Marshal 的字段。

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

准备工作:

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

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

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

如果你幸运地在这个过程中没有烧毁任何晶体管或断路器,你应该得到类似这样的结果:

  1. func MarshalOnlyFields(structa interface{},
  2. includeFields map[string]bool) (jsona []byte, status error) {
  3. value := reflect.ValueOf(structa)
  4. typa := reflect.TypeOf(structa)
  5. size := value.NumField()
  6. jsona = append(jsona, '{')
  7. for i := 0; i < size; i++ {
  8. structValue := value.Field(i)
  9. var fieldName string = typa.Field(i).Name
  10. if marshalledField, marshalStatus := json.Marshal((structValue).Interface()); marshalStatus != nil {
  11. return []byte{}, marshalStatus
  12. } else {
  13. if includeFields[fieldName] {
  14. jsona = append(jsona, '"')
  15. jsona = append(jsona, []byte(fieldName)...)
  16. jsona = append(jsona, '"')
  17. jsona = append(jsona, ':')
  18. jsona = append(jsona, (marshalledField)...)
  19. if i+1 != len(includeFields) {
  20. jsona = append(jsona, ',')
  21. }
  22. }
  23. }
  24. }
  25. jsona = append(jsona, '}')
  26. return
  27. }

服务:

使用任意结构体和一个包含要包含的字段的 map[string]bool,例如

  1. type magic struct {
  2. Magic1 int
  3. Magic2 string
  4. Magic3 [2]int
  5. }
  6. func main() {
  7. var magic = magic{0, "tusia", [2]int{0, 1}}
  8. if json, status := MarshalOnlyFields(magic, map[string]bool{"Magic1": true}); status != nil {
  9. println("error")
  10. } else {
  11. fmt.Println(string(json))
  12. }
  13. }

Bon Appetit!

英文:

Take three ingredients:

  1. The reflect package to loop over all the fields of a struct.

  2. An if statement to pick up the fields you want to Marshal, and

  3. The encoding/json package to Marshal the fields of your liking.

Preparation:

  1. Blend them in a good proportion. Use reflect.TypeOf(your_struct).Field(i).Name() to get a name of the ith field of your_struct.

  2. Use reflect.ValueOf(your_struct).Field(i) to get a type Value representation of an ith field of your_struct.

  3. Use fieldValue.Interface() to retrieve the actual value (upcasted to type interface{}) of the fieldValue of type Value (note the bracket use - the Interface() method produces interface{}

If you luckily manage not to burn any transistors or circuit-breakers in the process you should get something like this:

  1. func MarshalOnlyFields(structa interface{},
  2. includeFields map[string]bool) (jsona []byte, status error) {
  3. value := reflect.ValueOf(structa)
  4. typa := reflect.TypeOf(structa)
  5. size := value.NumField()
  6. jsona = append(jsona, &#39;{&#39;)
  7. for i := 0; i &lt; size; i++ {
  8. structValue := value.Field(i)
  9. var fieldName string = typa.Field(i).Name
  10. if marshalledField, marshalStatus := json.Marshal((structValue).Interface()); marshalStatus != nil {
  11. return []byte{}, marshalStatus
  12. } else {
  13. if includeFields[fieldName] {
  14. jsona = append(jsona, &#39;&quot;&#39;)
  15. jsona = append(jsona, []byte(fieldName)...)
  16. jsona = append(jsona, &#39;&quot;&#39;)
  17. jsona = append(jsona, &#39;:&#39;)
  18. jsona = append(jsona, (marshalledField)...)
  19. if i+1 != len(includeFields) {
  20. jsona = append(jsona, &#39;,&#39;)
  21. }
  22. }
  23. }
  24. }
  25. jsona = append(jsona, &#39;}&#39;)
  26. return
  27. }

Serving:

serve with an arbitrary struct and a map[string]bool of fields you want to include, for example

  1. type magic struct {
  2. Magic1 int
  3. Magic2 string
  4. Magic3 [2]int
  5. }
  6. func main() {
  7. var magic = magic{0, &quot;tusia&quot;, [2]int{0, 1}}
  8. if json, status := MarshalOnlyFields(magic, map[string]bool{&quot;Magic1&quot;: true}); status != nil {
  9. println(&quot;error&quot;)
  10. } else {
  11. fmt.Println(string(json))
  12. }
  13. }

Bon Appetit!

答案6

得分: 9

我刚刚发布了sheriff,它可以根据在结构体字段上注释的标签将结构体转换为基于映射的形式。然后,您可以将生成的映射进行编组(JSON或其他格式)。它可能不允许您仅序列化调用者请求的字段集,但我想使用一组组可能可以涵盖大多数情况。与直接使用字段相比,使用组很可能还会增加缓存能力。

示例:

  1. package main
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "log"
  6. "github.com/hashicorp/go-version"
  7. "github.com/liip/sheriff"
  8. )
  9. type User struct {
  10. Username string `json:"username" groups:"api"`
  11. Email string `json:"email" groups:"personal"`
  12. Name string `json:"name" groups:"api"`
  13. Roles []string `json:"roles" groups:"api" since:"2"`
  14. }
  15. func main() {
  16. user := User{
  17. Username: "alice",
  18. Email: "alice@example.org",
  19. Name: "Alice",
  20. Roles: []string{"user", "admin"},
  21. }
  22. v2, err := version.NewVersion("2.0.0")
  23. if err != nil {
  24. log.Panic(err)
  25. }
  26. o := &sheriff.Options{
  27. Groups: []string{"api"},
  28. ApiVersion: v2,
  29. }
  30. data, err := sheriff.Marshal(o, user)
  31. if err != nil {
  32. log.Panic(err)
  33. }
  34. output, err := json.MarshalIndent(data, "", " ")
  35. if err != nil {
  36. log.Panic(err)
  37. }
  38. fmt.Printf("%s", output)
  39. }
英文:

I just published sheriff, which transforms structs to a map based on tags annotated on the struct fields. You can then marshal (JSON or others) the generated map. It probably doesn't allow you to only serialize the set of fields the caller requested, but I imagine using a set of groups would allow you to cover most cases. Using groups instead of the fields directly would most likely also increase cache-ability.

Example:

  1. package main
  2. import (
  3. &quot;encoding/json&quot;
  4. &quot;fmt&quot;
  5. &quot;log&quot;
  6. &quot;github.com/hashicorp/go-version&quot;
  7. &quot;github.com/liip/sheriff&quot;
  8. )
  9. type User struct {
  10. Username string `json:&quot;username&quot; groups:&quot;api&quot;`
  11. Email string `json:&quot;email&quot; groups:&quot;personal&quot;`
  12. Name string `json:&quot;name&quot; groups:&quot;api&quot;`
  13. Roles []string `json:&quot;roles&quot; groups:&quot;api&quot; since:&quot;2&quot;`
  14. }
  15. func main() {
  16. user := User{
  17. Username: &quot;alice&quot;,
  18. Email: &quot;alice@example.org&quot;,
  19. Name: &quot;Alice&quot;,
  20. Roles: []string{&quot;user&quot;, &quot;admin&quot;},
  21. }
  22. v2, err := version.NewVersion(&quot;2.0.0&quot;)
  23. if err != nil {
  24. log.Panic(err)
  25. }
  26. o := &amp;sheriff.Options{
  27. Groups: []string{&quot;api&quot;},
  28. ApiVersion: v2,
  29. }
  30. data, err := sheriff.Marshal(o, user)
  31. if err != nil {
  32. log.Panic(err)
  33. }
  34. output, err := json.MarshalIndent(data, &quot;&quot;, &quot; &quot;)
  35. if err != nil {
  36. log.Panic(err)
  37. }
  38. fmt.Printf(&quot;%s&quot;, output)
  39. }

答案7

得分: 8

我创建了这个函数来将结构体转换为JSON字符串,可以忽略一些字段。希望能对你有所帮助。

  1. func GetJSONString(obj interface{}, ignoreFields ...string) (string, error) {
  2. toJson, err := json.Marshal(obj)
  3. if err != nil {
  4. return "", err
  5. }
  6. if len(ignoreFields) == 0 {
  7. return string(toJson), nil
  8. }
  9. toMap := map[string]interface{}{}
  10. json.Unmarshal([]byte(string(toJson)), &toMap)
  11. for _, field := range ignoreFields {
  12. delete(toMap, field)
  13. }
  14. toJson, err = json.Marshal(toMap)
  15. if err != nil {
  16. return "", err
  17. }
  18. return string(toJson), nil
  19. }

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

英文:

I created this function to convert struct to JSON string by ignoring some fields. Hope it will help.

  1. func GetJSONString(obj interface{}, ignoreFields ...string) (string, error) {
  2. toJson, err := json.Marshal(obj)
  3. if err != nil {
  4. return &quot;&quot;, err
  5. }
  6. if len(ignoreFields) == 0 {
  7. return string(toJson), nil
  8. }
  9. toMap := map[string]interface{}{}
  10. json.Unmarshal([]byte(string(toJson)), &amp;toMap)
  11. for _, field := range ignoreFields {
  12. delete(toMap, field)
  13. }
  14. toJson, err = json.Marshal(toMap)
  15. if err != nil {
  16. return &quot;&quot;, err
  17. }
  18. return string(toJson), nil
  19. }

Example: https://play.golang.org/p/nmq7MFF47Gp

答案8

得分: 5

你可以使用标记属性"omitifempty"或将可选字段设置为指针,并将要跳过的字段保持未初始化。

英文:

You can use tagging attribute "omitifempty" or make optional fields pointers and leave those you want skipped uninitialized.

答案9

得分: 5

这是我定义结构的方式。

  1. type User struct {
  2. Username string `json:"username" bson:"username"`
  3. Email string `json:"email" bson:"email"`
  4. Password *string `json:"password,omitempty" bson:"password"`
  5. FullName string `json:"fullname" bson:"fullname"`
  6. }

在我的函数中,使用user.Password = nil 来防止被编组。

英文:

Here is how I defined my structure.

  1. type User struct {
  2. Username string `json:&quot;username&quot; bson:&quot;username&quot;`
  3. Email string `json:&quot;email&quot; bson:&quot;email&quot;`
  4. Password *string `json:&quot;password,omitempty&quot; bson:&quot;password&quot;`
  5. FullName string `json:&quot;fullname&quot; bson:&quot;fullname&quot;`
  6. }

And inside my function set user.Password = nil for not to be Marshalled.

答案10

得分: 3

这个问题现在有点老了,但是我之前遇到了同样的问题,由于找不到简单的方法来解决这个问题,所以我构建了一个满足这个目的的库。
它可以轻松地从一个静态结构生成一个map[string]interface{}

https://github.com/tuvistavie/structomap

英文:

The question is now a bit old, but I came across the same issue a little while ago, and as I found no easy way to do this, I built a library fulfilling this purpose.
It allows to easily generate a map[string]interface{} from a static struct.

https://github.com/tuvistavie/structomap

答案11

得分: 3

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

  1. type User2 struct {
  2. ID int `groups:"id" json:"id,omitempty"`
  3. Username string `groups:"username" json:"username,omitempty"`
  4. Nickname string `groups:"nickname" json:"nickname,omitempty"`
  5. }
  6. type User struct {
  7. ID int `groups:"private,public" json:"id,omitempty"`
  8. Username string `groups:"private" json:"username,omitempty"`
  9. Nickname string `groups:"public" json:"nickname,omitempty"`
  10. }
  11. var (
  12. tagName = "groups"
  13. )
  14. //OmitFields通过检查它们的标签组值和访问控制标签(acTags)将字段设置为nil
  15. func OmitFields(obj interface{}, acTags []string) {
  16. //nilV := reflect.Value{}
  17. sv := reflect.ValueOf(obj).Elem()
  18. st := sv.Type()
  19. if sv.Kind() == reflect.Struct {
  20. for i := 0; i < st.NumField(); i++ {
  21. fieldVal := sv.Field(i)
  22. if fieldVal.CanSet() {
  23. tagStr := st.Field(i).Tag.Get(tagName)
  24. if len(tagStr) == 0 {
  25. continue
  26. }
  27. tagList := strings.Split(strings.Replace(tagStr, " ", "", -1), ",")
  28. //fmt.Println(tagList)
  29. // ContainsCommonItem检查数组中是否至少有一个公共项
  30. if !ContainsCommonItem(tagList, acTags) {
  31. fieldVal.Set(reflect.Zero(fieldVal.Type()))
  32. }
  33. }
  34. }
  35. }
  36. }
  37. //ContainsCommonItem检查数组是否至少有一个相等的项
  38. func ContainsCommonItem(arr1 []string, arr2 []string) bool {
  39. for i := 0; i < len(arr1); i++ {
  40. for j := 0; j < len(arr2); j++ {
  41. if arr1[i] == arr2[j] {
  42. return true
  43. }
  44. }
  45. }
  46. return false
  47. }
  48. func main() {
  49. u := User{ID: 1, Username: "very secret", Nickname: "hinzir"}
  50. //假设经过身份验证的用户没有权限访问私有字段
  51. OmitFields(&u, []string{"public"})
  52. bytes, _ := json.Marshal(&u)
  53. fmt.Println(string(bytes))
  54. u2 := User2{ID: 1, Username: "very secret", Nickname: "hinzir"}
  55. //你想通过字段名来过滤字段
  56. OmitFields(&u2, []string{"id", "nickname"})
  57. bytes, _ = json.Marshal(&u2)
  58. fmt.Println(string(bytes))
  59. }
英文:

I didn't have the same problem but similar. Below code solves your problem too, of course if you don't mind performance issue. Before implement that kind of solution to your system I recommend you to redesign your structure if you can. Sending variable structure response is over-engineering. I believe a response structure represents a contract between a request and resource and it should't be depend requests.(you can make un-wanted fields null, I do). In some cases we have to implement this design, if you believe you are in that cases here is the play link and code I use.

  1. type User2 struct {
  2. ID int `groups:&quot;id&quot; json:&quot;id,omitempty&quot;`
  3. Username string `groups:&quot;username&quot; json:&quot;username,omitempty&quot;`
  4. Nickname string `groups:&quot;nickname&quot; json:&quot;nickname,omitempty&quot;`
  5. }
  6. type User struct {
  7. ID int `groups:&quot;private,public&quot; json:&quot;id,omitempty&quot;`
  8. Username string `groups:&quot;private&quot; json:&quot;username,omitempty&quot;`
  9. Nickname string `groups:&quot;public&quot; json:&quot;nickname,omitempty&quot;`
  10. }
  11. var (
  12. tagName = &quot;groups&quot;
  13. )
  14. //OmitFields sets fields nil by checking their tag group value and access control tags(acTags)
  15. func OmitFields(obj interface{}, acTags []string) {
  16. //nilV := reflect.Value{}
  17. sv := reflect.ValueOf(obj).Elem()
  18. st := sv.Type()
  19. if sv.Kind() == reflect.Struct {
  20. for i := 0; i &lt; st.NumField(); i++ {
  21. fieldVal := sv.Field(i)
  22. if fieldVal.CanSet() {
  23. tagStr := st.Field(i).Tag.Get(tagName)
  24. if len(tagStr) == 0 {
  25. continue
  26. }
  27. tagList := strings.Split(strings.Replace(tagStr, &quot; &quot;, &quot;&quot;, -1), &quot;,&quot;)
  28. //fmt.Println(tagList)
  29. // ContainsCommonItem checks whether there is at least one common item in arrays
  30. if !ContainsCommonItem(tagList, acTags) {
  31. fieldVal.Set(reflect.Zero(fieldVal.Type()))
  32. }
  33. }
  34. }
  35. }
  36. }
  37. //ContainsCommonItem checks if arrays have at least one equal item
  38. func ContainsCommonItem(arr1 []string, arr2 []string) bool {
  39. for i := 0; i &lt; len(arr1); i++ {
  40. for j := 0; j &lt; len(arr2); j++ {
  41. if arr1[i] == arr2[j] {
  42. return true
  43. }
  44. }
  45. }
  46. return false
  47. }
  48. func main() {
  49. u := User{ID: 1, Username: &quot;very secret&quot;, Nickname: &quot;hinzir&quot;}
  50. //assume authenticated user doesn&#39;t has permission to access private fields
  51. OmitFields(&amp;u, []string{&quot;public&quot;})
  52. bytes, _ := json.Marshal(&amp;u)
  53. fmt.Println(string(bytes))
  54. u2 := User2{ID: 1, Username: &quot;very secret&quot;, Nickname: &quot;hinzir&quot;}
  55. //you want to filter fields by field names
  56. OmitFields(&amp;u2, []string{&quot;id&quot;, &quot;nickname&quot;})
  57. bytes, _ = json.Marshal(&amp;u2)
  58. fmt.Println(string(bytes))
  59. }

答案12

得分: 3

我也遇到了这个问题,起初我只是想在我的http处理程序中专门处理响应。我的第一个方法是创建一个包,将一个结构体的信息复制到另一个结构体中,然后将第二个结构体进行编组。我使用反射来实现这个包,所以从来不喜欢这种方法,而且我也不是动态的。

所以我决定修改encoding/json包来实现这个。函数MarshalMarshalIndent(Encoder) Encode还接收一个

type F map[string]F

我想模拟一个JSON,其中只有需要编组的字段,所以它只编组映射中的字段。

https://github.com/jtorz/jsont

  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "net/http"
  6. "github.com/jtorz/jsont/v2"
  7. )
  8. type SearchResult struct {
  9. Date string `json:"date"`
  10. IdCompany int `json:"idCompany"`
  11. Company string `json:"company"`
  12. IdIndustry interface{} `json:"idIndustry"`
  13. Industry string `json:"industry"`
  14. IdContinent interface{} `json:"idContinent"`
  15. Continent string `json:"continent"`
  16. IdCountry interface{} `json:"idCountry"`
  17. Country string `json:"country"`
  18. IdState interface{} `json:"idState"`
  19. State string `json:"state"`
  20. IdCity interface{} `json:"idCity"`
  21. City string `json:"city"`
  22. } //SearchResult
  23. type SearchResults struct {
  24. NumberResults int `json:"numberResults"`
  25. Results []SearchResult `json:"results"`
  26. } //type SearchResults
  27. func main() {
  28. msg := SearchResults{
  29. NumberResults: 2,
  30. Results: []SearchResult{
  31. {
  32. Date: "12-12-12",
  33. IdCompany: 1,
  34. Company: "alfa",
  35. IdIndustry: 1,
  36. Industry: "IT",
  37. IdContinent: 1,
  38. Continent: "america",
  39. IdCountry: 1,
  40. Country: "México",
  41. IdState: 1,
  42. State: "CDMX",
  43. IdCity: 1,
  44. City: "Atz",
  45. },
  46. {
  47. Date: "12-12-12",
  48. IdCompany: 2,
  49. Company: "beta",
  50. IdIndustry: 1,
  51. Industry: "IT",
  52. IdContinent: 1,
  53. Continent: "america",
  54. IdCountry: 2,
  55. Country: "USA",
  56. IdState: 2,
  57. State: "TX",
  58. IdCity: 2,
  59. City: "XYZ",
  60. },
  61. },
  62. }
  63. fmt.Println(msg)
  64. http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
  65. //{"numberResults":2,"results":[{"date":"12-12-12","idCompany":1,"idIndustry":1,"country":"México"},{"date":"12-12-12","idCompany":2,"idIndustry":1,"country":"USA"}]}
  66. err := jsont.NewEncoder(w).Encode(msg, jsont.F{
  67. "numberResults": nil,
  68. "results": jsont.F{
  69. "date": nil,
  70. "idCompany": nil,
  71. "idIndustry": nil,
  72. "country": nil,
  73. },
  74. })
  75. if err != nil {
  76. log.Fatal(err)
  77. }
  78. })
  79. http.ListenAndServe(":3009", nil)
  80. }
英文:

I also faced this problem, at first I just wanted to specialize the responses in my http handler. My first approach was creating a package that copies the information of a struct to another struct and then marshal that second struct. I did that package using reflection, so, never liked that approach and also I wasn't dynamically.

So I decided to modify the encoding/json package to do this. The functions Marshal, MarshalIndent and (Encoder) Encode additionally receives a

type F map[string]F

I wanted to simulate a JSON of the fields that are needed to marshal, so it only marshals the fields that are in the map.

https://github.com/jtorz/jsont

  1. package main
  2. import (
  3. &quot;fmt&quot;
  4. &quot;log&quot;
  5. &quot;net/http&quot;
  6. &quot;github.com/jtorz/jsont/v2&quot;
  7. )
  8. type SearchResult struct {
  9. Date string `json:&quot;date&quot;`
  10. IdCompany int `json:&quot;idCompany&quot;`
  11. Company string `json:&quot;company&quot;`
  12. IdIndustry interface{} `json:&quot;idIndustry&quot;`
  13. Industry string `json:&quot;industry&quot;`
  14. IdContinent interface{} `json:&quot;idContinent&quot;`
  15. Continent string `json:&quot;continent&quot;`
  16. IdCountry interface{} `json:&quot;idCountry&quot;`
  17. Country string `json:&quot;country&quot;`
  18. IdState interface{} `json:&quot;idState&quot;`
  19. State string `json:&quot;state&quot;`
  20. IdCity interface{} `json:&quot;idCity&quot;`
  21. City string `json:&quot;city&quot;`
  22. } //SearchResult
  23. type SearchResults struct {
  24. NumberResults int `json:&quot;numberResults&quot;`
  25. Results []SearchResult `json:&quot;results&quot;`
  26. } //type SearchResults
  27. func main() {
  28. msg := SearchResults{
  29. NumberResults: 2,
  30. Results: []SearchResult{
  31. {
  32. Date: &quot;12-12-12&quot;,
  33. IdCompany: 1,
  34. Company: &quot;alfa&quot;,
  35. IdIndustry: 1,
  36. Industry: &quot;IT&quot;,
  37. IdContinent: 1,
  38. Continent: &quot;america&quot;,
  39. IdCountry: 1,
  40. Country: &quot;M&#233;xico&quot;,
  41. IdState: 1,
  42. State: &quot;CDMX&quot;,
  43. IdCity: 1,
  44. City: &quot;Atz&quot;,
  45. },
  46. {
  47. Date: &quot;12-12-12&quot;,
  48. IdCompany: 2,
  49. Company: &quot;beta&quot;,
  50. IdIndustry: 1,
  51. Industry: &quot;IT&quot;,
  52. IdContinent: 1,
  53. Continent: &quot;america&quot;,
  54. IdCountry: 2,
  55. Country: &quot;USA&quot;,
  56. IdState: 2,
  57. State: &quot;TX&quot;,
  58. IdCity: 2,
  59. City: &quot;XYZ&quot;,
  60. },
  61. },
  62. }
  63. fmt.Println(msg)
  64. http.HandleFunc(&quot;/&quot;, func(w http.ResponseWriter, r *http.Request) {
  65. //{&quot;numberResults&quot;:2,&quot;results&quot;:[{&quot;date&quot;:&quot;12-12-12&quot;,&quot;idCompany&quot;:1,&quot;idIndustry&quot;:1,&quot;country&quot;:&quot;M&#233;xico&quot;},{&quot;date&quot;:&quot;12-12-12&quot;,&quot;idCompany&quot;:2,&quot;idIndustry&quot;:1,&quot;country&quot;:&quot;USA&quot;}]}
  66. err := jsont.NewEncoder(w).Encode(msg, jsont.F{
  67. &quot;numberResults&quot;: nil,
  68. &quot;results&quot;: jsont.F{
  69. &quot;date&quot;: nil,
  70. &quot;idCompany&quot;: nil,
  71. &quot;idIndustry&quot;: nil,
  72. &quot;country&quot;: nil,
  73. },
  74. })
  75. if err != nil {
  76. log.Fatal(err)
  77. }
  78. })
  79. http.ListenAndServe(&quot;:3009&quot;, nil)
  80. }

答案13

得分: 1

<h1>根据结构体中的另一个字段动态更新/删除序列化的字段</h1>
<h2>也就是说,如果Hidden字段为true,则不序列化Content字段。或者,如果Hidden字段为true,则将Content设置为“未找到任何内容!”</h2>

<br>

<h4>示例:</h4>

对于以下结构体(其中CustomStruct是您拥有的任何自定义结构体):

  1. type Post struct {
  2. Hidden bool `json:"hidden"`
  3. Content string `json:"content"`
  4. Value CustomStruct `json:"value"`
  5. }

您可以根据Hidden的布尔值动态返回或不返回任何字段的值。

首先,为您的结构体创建一个别名:

  1. type Alias Post

然后为该别名创建一个包装器:

  1. type AuxPost struct {
  2. *Alias
  3. }

然后重写MarshalJSON方法(导入"encoding/json"包):

  1. func (p Post) MarshalJSON() ([]byte, error) {
  2. type Alias Post
  3. if p.Hidden {
  4. hiddenPost := struct {
  5. Alias
  6. Content string `json:"content"`
  7. Value bool `json:"value,omitempty"`
  8. }{
  9. Alias: (Alias)(p),
  10. Content: "[this post was removed]",
  11. Value: false,
  12. }
  13. return json.Marshal(hiddenPost)
  14. }
  15. regularPost := struct {
  16. Alias
  17. }{
  18. Alias: (Alias)(p),
  19. }
  20. return json.Marshal(regularPost)
  21. }

如果帖子的Hidden字段为true,则它将正常序列化。如果不是,则Content字段将被设置为"[this post was removed]",并且Value字段将被完全省略(因为在这种特殊情况下我设置了omitempty)。

... Hidden为false时的序列化结果为:

  1. {
  2. "hidden": true,
  3. "content": "hello world",
  4. "value": {
  5. "note": "your custom struct can have anything"
  6. }
  7. }

... Hidden为true时的序列化结果为:

  1. {
  2. "hidden": true,
  3. "content": "[this post was removed]"
  4. }

删除非结构体字段很简单,但是删除结构体的方式是通过欺骗它,使其认为它是一个布尔值,然后将该布尔值设置为false,并从序列化中省略零值(布尔值的零值为false)。

编辑:如果我们希望Value结构体只被序列化为null,我们需要删除omitempty标签,并将其类型(之前为bool)设置为*interface{},然后将其设置为nil(之前为false)。

英文:

<h1>Update/remove struct fields from serialization based on another field in that struct dynamically</h1>
<h2>AKA: Don't serialize the Content field if the Hidden field is true. Or, Set Content to "nothing found!" if the Hidden field is true.</h2>

<br>

<h4>Example:</h4>

For this struct (where CustomStruct is any custom struct you have):

  1. type Post struct {
  2. Hidden bool `json:&quot;hidden&quot;`
  3. Content string `json:&quot;content&quot;`
  4. Value CustomStruct `json:&quot;value&quot;`
  5. }

You can choose to dynamically return or not the value of any field based on the bool value of Hidden.

First, create an alias for your struct:

  1. type Alias Post

Then make a wrapper for the alias:

  1. type AuxPost struct {
  2. *Alias
  3. }

Then override the MarshalJSON method (importing package &quot;encoding/json&quot;):

  1. func (p Post) MarshalJSON() ([]byte, error) {
  2. type Alias Post
  3. if p.Hidden {
  4. hiddenPost := struct {
  5. Alias
  6. Content string `json:&quot;content&quot;`
  7. Value bool `json:&quot;value,omitempty&quot;`
  8. }{
  9. Alias: (Alias)(p),
  10. Content: &quot;[this post was removed]&quot;,
  11. Value: false,
  12. }
  13. return json.Marshal(hiddenPost)
  14. }
  15. regularPost := struct {
  16. Alias
  17. }{
  18. Alias: (Alias)(p),
  19. }
  20. return json.Marshal(regularPost)
  21. }

If your post's Hidden field is true, then it will serialize normally. If not, the Content field will be set to &quot;[this post was removed]&quot; and the Value field will be completely omitted (because in this particular case I set omitempty).

... the case where Hidden is false serializes as:

  1. {
  2. &quot;hidden&quot;: true,
  3. &quot;content&quot;: &quot;hello world&quot;,
  4. &quot;value&quot;: {
  5. &quot;note&quot;: &quot;your custom struct can have anything&quot;
  6. }
  7. }

... and the case where Hidden is true serializes as:

  1. {
  2. &quot;hidden&quot;: true,
  3. &quot;content&quot;: &quot;[this post was removed]&quot;
  4. }

Removing a non-struct field is trivial, but the way the struct getting removed works is by tricking it into thinking its a boolean, and then setting that bool to false, and omitting zero values from serializations (bool's zero-value = false).

EDIT: If we wanted the Value struct to just be serialized as null instead, we got remove the omitempty tag and instead set its type (previously bool) to *interface{} and then below as nil (previously false).

答案14

得分: 0

为了扩展chhaileng的答案,这里是一个使用递归删除所有出现的字段的版本

  1. // GetJSONWithOutFields - 描述:返回一个删除指定字段的接口的字符串表示
  2. func GetJSONWithOutFields(obj interface{}, ignoreFields ...string) (string, error) {
  3. toJson, err := json.Marshal(obj)
  4. if err != nil {
  5. return "", err
  6. }
  7. if len(ignoreFields) == 0 {
  8. return string(toJson), nil
  9. }
  10. toMap := map[string]interface{}{}
  11. err = json.Unmarshal(toJson, &toMap)
  12. if err != nil {
  13. return "", err
  14. }
  15. for _, field := range ignoreFields {
  16. DeleteField(toMap, field)
  17. }
  18. toJson, err = json.Marshal(toMap)
  19. if err != nil {
  20. return "", err
  21. }
  22. return string(toJson), nil
  23. }
  24. // DeleteField - 描述:递归删除字段
  25. func DeleteField(toMap map[string]interface{}, field string) {
  26. delete(toMap, field)
  27. for _, v := range toMap {
  28. if m, isMap := v.(map[string]interface{}); isMap {
  29. DeleteField(m, field)
  30. }
  31. }
  32. }
英文:

To extend chhaileng answer, here is the version that remove all occurrences of a field with recursion

  1. // GetJSONWithOutFields - Description: return a string representation of an interface with specified fields removed
  2. func GetJSONWithOutFields(obj interface{}, ignoreFields ...string) (string, error) {
  3. toJson, err := json.Marshal(obj)
  4. if err != nil {
  5. return &quot;&quot;, err
  6. }
  7. if len(ignoreFields) == 0 {
  8. return string(toJson), nil
  9. }
  10. toMap := map[string]interface{}{}
  11. err = json.Unmarshal(toJson, &amp;toMap)
  12. if err != nil {
  13. return &quot;&quot;, err
  14. }
  15. for _, field := range ignoreFields {
  16. DeleteField(toMap, field)
  17. }
  18. toJson, err = json.Marshal(toMap)
  19. if err != nil {
  20. return &quot;&quot;, err
  21. }
  22. return string(toJson), nil
  23. }
  24. // DeleteField - Description: recursively delete field
  25. func DeleteField(toMap map[string]interface{}, field string) {
  26. delete(toMap, field)
  27. for _, v := range toMap {
  28. if m, isMap := v.(map[string]interface{}); isMap {
  29. DeleteField(m, field)
  30. }
  31. }
  32. }

答案15

得分: 0

使用带有omitempty的指针:

  1. package main
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. )
  6. type Foo struct {
  7. N int `json:"n,omitempty"`
  8. S string `json:"s,omitempty"`
  9. }
  10. type Bar struct {
  11. N *int `json:"n,omitempty"`
  12. S *string `json:"s,omitempty"`
  13. }
  14. func main() {
  15. var zero int
  16. var empty string
  17. foo := Foo{
  18. N: zero,
  19. S: empty,
  20. }
  21. data, _ := json.Marshal(foo)
  22. fmt.Println(string(data)) // 输出 {}
  23. bar := Bar{
  24. N: &zero,
  25. S: &empty,
  26. }
  27. data, _ = json.Marshal(bar)
  28. fmt.Println(string(data)) // 输出 {"n":0,"s":""}
  29. }

omitempty会省略零值,而指向某个变量的指针不是指针的零值,即使被指向的变量是零值。要从JSON中省略值,请将指针设置为nil(或者不要初始化它)。

英文:

Use pointers with omitempty:

  1. package main
  2. import (
  3. &quot;encoding/json&quot;
  4. &quot;fmt&quot;
  5. )
  6. type Foo struct {
  7. N int `json:&quot;n,omitempty&quot;`
  8. S string `json:&quot;s,omitempty&quot;`
  9. }
  10. type Bar struct {
  11. N *int `json:&quot;n,omitempty&quot;`
  12. S *string `json:&quot;s,omitempty&quot;`
  13. }
  14. func main() {
  15. var zero int
  16. var empty string
  17. foo := Foo{
  18. N: zero,
  19. S: empty,
  20. }
  21. data, _ := json.Marshal(foo)
  22. fmt.Println(string(data)) // prints {}
  23. bar := Bar{
  24. N: &amp;zero,
  25. S: &amp;empty,
  26. }
  27. data, _ = json.Marshal(bar)
  28. fmt.Println(string(data)) // prints {&quot;n&quot;:0,&quot;s&quot;:&quot;&quot;}
  29. }

omitempty omits zero values, and pointer to something is not a zero value of pointer, even if variable pointed has zero value. To omit value from JSON set the pointer to nil (or just don't initialize it)

huangapple
  • 本文由 发表于 2013年6月26日 03:56:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/17306358.html
匿名

发表评论

匿名网友

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

确定