web-dev-qa-db-ja.com

JSON応答で構造体からフィールドを削除または非表示にする

GoでAPIを作成し、呼び出されると、クエリを実行し、構造体のインスタンスを作成し、その構造体をJSONとしてエンコードしてから呼び出し元に送り返します。ここで、呼び出し側が、「フィールド」GETパラメーターを渡すことによって返される特定のフィールドを選択できるようにしたいと思います。

これは、フィールドの値に応じて、構造体が変わることを意味します。構造体からフィールドを削除する方法はありますか?または、少なくともJSON応答で動的に非表示にしますか? (注:時々空の値があるため、ここではJSONのexcludeEmptyタグは機能しません)これらのどちらも可能でない場合、これを処理するより良い方法の提案はありますか?前もって感謝します。

私が使用している構造体の小さいバージョンは次のとおりです。

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

編集:私はいくつかのダウン票に気づき、このQ&Aをもう一度見ました。ほとんどの人は、OPがdynamicallyのフィールドを呼び出し元が提供したフィールドのリストに基づいて選択するように要求したことを忘れているようです。静的に定義されたjson structタグを使用してこれを行うことはできません。

alwaysを使用してjson-encodeにフィールドをスキップする場合は、もちろんjson:"-"を使用してフィールドを無視します(フィールドがnotの場合は必須です)未エクスポート-これらのフィールドは、jsonエンコーダーによって常に無視されます)。しかし、それはOPの問題ではありません。

json:"-"回答のコメントを引用するには:

この[json:"-"回答]は、ほとんどの人がここで検索することを望まないであろう回答ですが、それは質問に対する答えではありません。


この場合、構造体の代わりにmap [string] interface {}を使用します。削除するフィールドのマップでdeleteビルトインを呼び出すことにより、フィールドを簡単に削除できます。

つまり、最初に要求されたフィールドのみを照会できない場合です。

209
mna

`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

137
GivenJazz

これを行うもう1つの方法は、,omitemptyタグでpointersの構造体を持つことです。ポインターがnilの場合、フィールドはマーシャリングされません。

この方法では、追加のリフレクションやマップの非効率的な使用は必要ありません。

このメソッドを使用したjorelliと同じ例: http://play.golang.org/p/JJNa0m2_nw

46
Druska

reflectパッケージを使用して、フィールドタグを反映し、jsonタグの値を選択することで、必要なフィールドを選択できます。必要なフィールドを選択し、それらをmap[string]interface{}として返すメソッドをSearchResultsタイプに定義し、SearchResults構造体自体の代わりにマーシャリングthatします。そのメソッドを定義する方法の例を次に示します。

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

12
jorelli

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:    "[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)
}
5
Michael Weibel

3つの材料を用意してください:

  1. 構造体のすべてのフィールドをループするreflectパッケージ。

  2. ifにしたいフィールドを選択するMarshalステートメント、および

  3. あなたの好みのフィールドにMarshalするencoding/jsonパッケージ。

準備:

  1. それらを適切な割合でブレンドします。 reflect.TypeOf(your_struct).Field(i).Name()を使用して、your_structithフィールドの名前を取得します。

  2. reflect.ValueOf(your_struct).Field(i)を使用して、your_structValuethフィールドのタイプi表現を取得します。

  3. fieldValue.Interface()を使用して、タイプfieldValueValueの実際の値(タイプinterface {}にキャスト)を取得します(ブラケットの使用に注意してください-Interface()methodinterface{}を生成します)

幸運なことに、プロセスでトランジスタや回路ブレーカーを燃やさないように管理している場合、次のようなものが得られます。

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
Adam Kurkiewicz

タグ属性「omitifempty」を使用するか、オプションのフィールドポインターを作成し、スキップしたいフィールドを初期化しないでおくことができます。

3
deemok

質問は今や少し古いですが、私は少し前に同じ問題に遭遇し、これを行う簡単な方法を見つけられなかったので、この目的を満たすライブラリを構築しました。静的構造体からmap[string]interface{}を簡単に生成できます。

https://github.com/tuvistavie/structomap

1
Daniel Perez

同じ問題はありませんでしたが、似ています。もちろん、パフォーマンスの問題を気にしないのであれば、以下のコードでも問題を解決できます。その種のソリューションをシステムに実装する前に、可能であれば構造を再設計することをお勧めします。変数構造の応答を送信するのは過剰です。応答構造は、リクエストとリソース間のコントラクトを表し、依存するリクエストであってはなりません(不要なフィールドをnullにすることもできます)。場合によっては、この設計を実装する必要があります。その場合、ここに play link と私が使用するコードがあります。

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

}
1
RockOnGom

また、この問題に直面しました。最初は、httpハンドラーで応答を特化したかっただけです。最初のアプローチは、構造体の情報を別の構造体にコピーし、その2番目の構造体をマーシャリングするパッケージを作成することでした。私はリフレクションを使用してそのパッケージを作成しました。そのため、このアプローチは好きではなく、動的でもありませんでした。

そこで、これを行うために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)
}
0
Juan Torres