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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/17306358/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 19:53:45  来源:igfitidea点击:

Removing fields from struct or hiding them in JSON Response

jsongo

提问by user387049

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.

我在 Go 中创建了一个 API,它在被调用时执行查询,创建一个结构体的实例,然后将该结构体编码为 JSON,然后再发送回调用者。我现在希望调用者能够通过传入“字段”GET 参数来选择他们想要返回的特定字段。

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? Thanks in advance.

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

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

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

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

I then encode and output the response like so:

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

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

回答by mna

EDIT: I noticed a few downvotes and took another look at this Q&A. Most people seem to miss that the OP asked for fields to be dynamicallyselected based on the caller-provided list of fields. You can't do this with the statically-defined json struct tag.

编辑:我注意到一些反对意见,并再次查看了这个问答。大多数人似乎都没有注意到 OP 要求根据调用者提供的字段列表动态选择字段。您不能使用静态定义的 json 结构标记来执行此操作。

If what you want is to alwaysskip a field to json-encode, then of course use json:"-"to ignore the field (also note that this is notrequired if your field is unexported - those fields are always ignored by the json encoder). But that is not the OP's question.

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

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

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

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.

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



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

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

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

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

回答by GivenJazz

use `json:"-"`

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

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

回答by Druska

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

另一种方法是使用带有标记的指针结构,omitempty。如果指针为nil,则不会对字段进行编组。

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

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

回答by jorelli

You can use the reflectpackage to select the fields that you want by reflecting on the field tags and selecting the jsontag 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 thatinstead of the SearchResults struct itself. Here's an example of how you might define that method:

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

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

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

回答by Adam Kurkiewicz

Take three ingredients:

取三种成分:

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

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

  3. The encoding/jsonpackage to Marshalthe fields of your liking.

  1. reflect超过一个结构的所有字段包循环。

  2. if选择您想要的字段的语句Marshal,以及

  3. encoding/jsonMarshal你喜欢的领域。

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 Valuerepresentation of an ith field of your_struct.

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

  1. 以适当的比例混合它们。使用reflect.TypeOf(your_struct).Field(i).Name()得到的名字i的日场your_struct

  2. 使用reflect.ValueOf(your_struct).Field(i)得到一个类型Value的代表性i的日场your_struct

  3. 使用fieldValue.Interface()检索实际值(upcasted到类型接口{})的fieldValue类型的Value(注意支架使用-所述接口()方法产生interface{}

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

如果您幸运地在此过程中没有烧毁任何晶体管或断路器,您应该得到如下结果:

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
}

Serving:

服务:

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

例如,使用任意结构和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!

好胃口!

回答by Michael Weibel

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.

我刚刚发布了sheriff,它根据结构字段上注释的标签将结构转换为地图。然后,您可以编组(JSON 或其他)生成的地图。它可能不允许您只序列化调用者请求的字段集,但我想使用一组组可以让您涵盖大多数情况。直接使用组而不是字段也很可能会增加缓存能力。

Example:

例子:

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:    "[email protected]",
        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)
}

回答by deemok

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

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

回答by Juan Torres

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.

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

So I decided to modify the encoding/json package to do this. The functions Marshal, MarshalIndentand (Encoder) Encodeadditionally receives a

所以我决定修改 encoding/json 包来做到这一点。的功能MarshalMarshalIndent(Encoder) Encode额外地接收

type F map[string]F

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.

我想模拟需要编组的字段的 JSON,因此它只编组地图中的字段。

https://github.com/JuanTorr/jsont

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

回答by Chhaileng

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

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

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

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

回答by RockOnGom

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 linkand code I use.

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

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

}