構造体からフィールドを削除するか、JSONレスポンスでフィールドを非表示にする


181

GoでAPIを作成しました。この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)

7
@ Jacob、PuerkitoBioの更新された回答に従って、私はあなたが質問を誤って読んだと思います。(現在)受け入れられているのは質問に対する「正解」ではないかもしれませんが、ここで尋ねられたものに対するものです!(現在)最高投票数の回答があなたの質問に答えるかもしれませんが、これには完全に適用できません!
デイブC

回答:


275

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

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

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

この[ json:"-"答え]は、ほとんどの人がここで検索を行って求める答えですが、質問に対する答えではありません。


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

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


4
ほとんどの場合、型定義を完全に破棄したくないでしょう。これらのフィールドにアクセスするこのタイプの他のメソッドを記述したい場合など、これは面倒な作業になります。中間体を使用するmap[string]interface{}ことには意味がありますが、型定義を破棄する必要はありません。
jorelli 2013年

1
もう1つの答えは、この質問に対する実際の答えです。
ジェイコブ

1
削除の考えられる欠点は、構造体(マップ)の複数のjsonビューをサポートしたい場合があることです。たとえば、機密フィールドのないクライアントのjsonビューと、機密フィールドのあるデータベースのjsonビュー。幸いにも、構造体を使用することはまだ可能です-私の答えを見てください。
Adam Kurkiewicz、2015

特定のものIdが必要なだけで、json構造体全体を返したくないので、これは私にとってはうまくいきます。これをありがとう!
ルイミランダ

155

`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


14
OPはAPIへのクエリ文字列エントリに基づいて出力フィールドを動的に制御したいと言っていたので、@ Jacobには同意しません。たとえば、APIの呼び出し元が業界と国のみを要求する場合は、残りを削除する必要があります。これが、「チェックされた」回答がこの質問への回答としてマークされている理由です。この非常に投票された答えは、フィールドを明示的に決して利用できない、組み込みのjsonマーシャラー-EVERにマークするためのものです。動的に必要な場合は、チェックされた答えが答えです。
eduncan911 2014年

11
これはほとんどの人が検索からここにたどり着くべき答えですが、それは質問に対する答えではありません。
Filip Haglund、2015年

5
すでに述べたように、OPはDTOを動的に形成する方法を求めていました。
codepushr

53

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

この方法では、追加の反射やマップの非効率的な使用は必要ありません。

この方法を使用したjorelliと同じ例:http ://play.golang.org/p/JJNa0m2_nw


3
+1完全に同意します。私は組み込みのマーシャラーで常にこのルール/トリックを使用しています(このルールに基づいてCSVリーダー/ライターを作成したこともあります!-すぐに別のcsv goパッケージをオープンソース化する可能性もあります)。OPは* Country値をnilに設定できず、省略されます。そして、あなたがnice.yと入力したplay.golangも提供したのは素晴らしいことです。
eduncan911 2014年

2
もちろん、その方法にはリフレクションが必要です。stdlibのjson-to-structマーシャリングは常にリフレクションを使用します(実際には常にリフレクション期間、マップ、構造体などを使用します)。
mna 2015年

はい。ただし、インターフェースを使用して追加のリフレクションを行う必要はありません。
Druska 2015年

14

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

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


考えてみると、selectfieldsパターンを任意のタイプと任意のタグキーに合理的に一般化できます。これについては、SearchResultの定義またはjsonキーに固有のものはありません。
ジョレッリ2013年

私はリフレクションを避けようとしていますが、これはタイプ情報をかなりうまく保存します... validate()メソッドのif / elseタグの束よりもあなたの構造がどのように見えるかを文書化するコードがあるのは素晴らしいです1つあります)
アクタウ2013年

7

構造体フィールドに注釈が付けられたタグに基づいて構造体をマップに変換する保安官を公開しました。次に、生成されたマップをマーシャリング(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)
}

7

3つの成分を取る:

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

  2. if必要なフィールドを選択するステートメントMarshal、および

  3. お好みの分野encoding/jsonへのパッケージMarshal

準備:

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

  2. のthフィールドのreflect.ValueOf(your_struct).Field(i)Value表現を取得するために使用します。iyour_struct

  3. 使用するfieldValue.Interface()実際の値を取得するための(タイプのインターフェース{}にupcasted)fieldValueタイプのValueインターフェース() - (ブラケット使用を注意方法で生成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たとえば、任意の構造体と、含めるフィールドのa を提供します。

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!


警告!includeFieldsに実際のフィールドと一致しないフィールド名が含まれている場合、無効なjsonを取得することになります。あなたは警告されました。
Adam Kurkiewicz、2015

5

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


これは、OPの質問と使用例に対する最も正しい答えです。
user1943442 2015年

2
@ user1943442、そうではありません。OP は、「omitempty」が適用できない理由を明示的に述べています。
Dave C、

2

私もこの問題に直面しました。最初は、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)
}

まだ試していませんが、見た目は素晴らしいです。Marshalerインターフェースもサポートされていると、さらに良いでしょう。
ハギー

1

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

https://github.com/tuvistavie/structomap


これで、私のレシピのコードスニペットを使用して簡単に実行できます。
Adam Kurkiewicz、2015

スニペットはライブラリのサブセットですが、を返すことに関するここでの主な問題[]byteは、それがあまり再利用できないことです。たとえば、後でフィールドを追加する簡単な方法はありません。そのためmap[string]interface{}、JSONシリアル化部分を作成して標準ライブラリに追加することをお勧めします。
Daniel Perez

1

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

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

一部のフィールドを無視してstructを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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.