IT박스

구조체에서 필드 제거 또는 JSON 응답에서 필드 숨기기

itboxs 2020. 6. 9. 22:18
반응형

구조체에서 필드 제거 또는 JSON 응답에서 필드 숨기기


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)

편집 : 나는 몇 가지 downvotes를 발견 하고이 Q & A를 다시 살펴 보았습니다. 대부분의 사람들은 OP가 발신자 제공 필드 목록을 기반 으로 필드를 동적으로 선택 하도록 요청한 것을 놓친 것 같습니다 . 정적으로 정의 된 json 구조체 태그로는이 작업을 수행 할 수 없습니다.

원하는 경우 항상 필드를 json-encode로 건너 뛰 려면 필드를 json:"-"무시하는 데 사용하십시오 (필드를 내 보내지 않은 경우 필요 하지 않음 -해당 필드는 항상 json 인코더에서 무시 됨). 그러나 그것은 OP의 질문이 아닙니다.

json:"-"답변 에 대한 의견을 인용하려면 :

이 [ json:"-"답변]은 대부분의 사람들이 여기서 검색을 마치고 싶어하는 대답이지만 질문에 대한 답은 아닙니다.


이 경우 구조체 대신 map [string] interface {}을 사용합니다. 필드에서 제거 할 수 있도록 delete맵에서 내장 을 호출하여 필드를 쉽게 제거 할 수 있습니다 .

즉, 처음에는 요청 된 필드에 대해서만 쿼리 할 수없는 경우입니다.


`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"`

의사 : http://golang.org/pkg/encoding/json/#Marshal


이 작업을 수행하는 또 다른 방법 태그 가있는 포인터 구조를 갖는 것 ,omitempty입니다. 포인터가 nil 인 경우 필드는 마샬링되지 않습니다.

이 방법은 추가 반영이나 비효율적 인 맵 사용이 필요하지 않습니다.

이 방법을 사용하는 jorelli와 동일한 예 : http://play.golang.org/p/JJNa0m2_nw


reflect패키지를 사용하여 필드 태그를 반영하고 json태그 값을 선택하여 원하는 필드를 선택할 수 있습니다 . 그 선택에게로 원하는 필드와 반환을 입력 당신의 SearchResult의 메소드를 정의 map[string]interface{}하고 마샬링 것을 의 SearchResult 자체를 구조체 대신. 해당 메소드를 정의하는 방법에 대한 예는 다음과 같습니다.

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


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

세 가지 성분을 섭취하십시오.

  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예를 들어 임의의 구조체와 포함하려는 필드를 제공하십시오.

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

}

많이 드세요!


태그 지정 속성 "omitifempty"를 사용하거나 선택적 필드 포인터를 만들고 생략하려는 필드를 초기화하지 않은 상태로 둘 수 있습니다.


문제는 이제 조금 오래되었지만 조금 전에 같은 문제를 겪어 왔으며 쉽게 할 수있는 방법을 찾지 못해서이 목적을 달성하는 라이브러리를 만들었습니다. map[string]interface{}정적 구조체에서 쉽게 생성 할 수 있습니다 .

https://github.com/tuvistavie/structomap


나는 같은 문제는 없었지만 비슷했다. 아래 코드는 성능 문제가 마음에 들지 않으면 문제를 해결합니다. 시스템에 이러한 종류의 솔루션을 구현하기 전에 가능하면 구조를 다시 디자인하는 것이 좋습니다. 가변 구조 응답을 보내는 것은 너무 공학적입니다. 응답 구조는 요청과 리소스 사이의 계약을 나타내며 의존 요청이어서는 안된다고 생각합니다 (원치 않는 필드를 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))

}

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.

So I decided to modify the encoding/json package to do this. The functions Marshal, MarshalIndent and (Encoder) Encode additionally receives a

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.

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

참고URL : https://stackoverflow.com/questions/17306358/removing-fields-from-struct-or-hiding-them-in-json-response

반응형