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

当前回答

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

其他回答

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

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

以三种成分为例:

用于遍历结构的所有字段的反射包。 一个if语句,用于拾取想要编组的字段,以及 encoding/json包来封送您喜欢的字段。

准备:

把它们按适当的比例混合。使用reflect.TypeOf(your_struct). field (i). name()来获取your_struct的第i个字段的名称。 使用reflect.ValueOf(your_struct). field (i)来获取your_struct的第i个字段的类型值表示。 使用fieldValue. interface()检索类型为value的fieldValue的实际值(转换为类型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))
    }

}

你好!

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

// GetJSONWithOutFields - Description: return a string representation of an interface with specified fields removed
func GetJSONWithOutFields(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{}{}
    err = json.Unmarshal(toJson, &toMap)
    if err != nil {
        return "", err
    }

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

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

// DeleteField - Description: recursively delete field
func DeleteField(toMap map[string]interface{}, field string) {
    delete(toMap, field)
    for _, v := range toMap {
        if m, isMap := v.(map[string]interface{}); isMap {
            DeleteField(m, field)
        }
    }
}

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.

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

}