从struct中删除字段或将其隐藏在JSON Response中

时间:2013-06-25 19:56:20

标签: json go

我在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)

12 个答案:

答案 0 :(得分:201)

编辑:我注意到了一些downvotes,又看了一下这个Q& A。大多数人似乎都错过了OP要求根据调用者提供的字段列表选择动态字段。您无法使用静态定义的json struct标记执行此操作。

如果你想要的是总是跳过字段到json-encode,那么当然要使用json:"-"忽略该字段(还要注意这是不是如果您的字段未导出则需要 - json编码器始终忽略这些字段。但这不是OP的问题。

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

  

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


在这种情况下,我会使用map [string] interface {}而不是struct。您可以通过调用地图上的delete内置字段来轻松删除字段,以便删除字段。

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

答案 1 :(得分:134)

使用`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

答案 2 :(得分:45)

另一种方法是使用,omitempty标记构建指针结构。如果指针是 nil ,则字段将不会被编组。

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

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

答案 3 :(得分:12)

您可以使用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

答案 4 :(得分:5)

采取三种成分:

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

  2. if语句,用于选择您想要的字段Marshal

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

  4. 的制备:将

    1. 以很高的比例混合它们。使用reflect.TypeOf(your_struct).Field(i).Name()获取i your_struct字段的名称。

    2. 使用reflect.ValueOf(your_struct).Field(i)获取Value i your_struct字段的fieldValue.Interface()类型。

    3. 使用fieldValue检索类型为Value的{​​{1}}的实际值(上传为界面{})(注意括号使用 - 接口()< em>方法生成interface{}

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

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

      Bon Appetit!

答案 5 :(得分:5)

我刚刚发布了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)
}

答案 6 :(得分:3)

您可以使用标记属性“omitifempty”或制作可选字段指针,并将您想要跳过的内容保留为未初始化。

答案 7 :(得分:1)

这个问题现在有点老了,但我刚才遇到了同样的问题,而且由于我发现没有简单的方法可以做到这一点,我建立了一个满足这个目的的图书馆。 它允许从静态结构中轻松生成map[string]interface{}

https://github.com/tuvistavie/structomap

答案 8 :(得分:1)

我创建了此函数,以通过忽略某些字段将结构转换为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

答案 9 :(得分:0)

我也遇到了这个问题,起初我只是想在我的http处理程序中专门化响应。我的第一种方法是创建一个程序包,将一个结构的信息复制到另一个结构,然后封送该第二个结构。我使用反射来完成该程序包,因此,从不喜欢这种方法,而且我也不是动态的。

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

type F map[string]F

我想模拟封送所需的字段的JSON,因此它仅封送地图中的字段。

https://github.com/JuanTorr/jsont

package main

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

    "github.com/JuanTorr/jsont"
)

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

答案 10 :(得分:0)

这是我定义结构的方式。

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中不要编组。

答案 11 :(得分:0)

其他答案已经说明,直接使用encoding/json软件包功能是不可能的。

相反,我想提供使用第三方库的替代解决方案。 免责声明:我是图书馆的作者。

https://github.com/wI2L/jettison

Jettison是一种更快的方法,可以灵活地替代encoding / json,同时默认情况下仍保持与标准库相同的行为。

您可以使用DenyList选项来过滤掉结果中应省略的字段。请注意,该功能是受限制的,并且仅适用于第一级字段。