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


181

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)

7
@Jacob는 PuerkitoBio의 업데이트 된 답변에 따라 질문을 잘못 읽은 것으로 생각합니다. (현재) 승인 된 것은 귀하의 질문에 대한 "정답"이 아닐 수 있지만 여기에있는 질문에 대한 것입니다! (현재) 가장 높은 투표 응답이 귀하의 질문에 대답 할 수도 있지만이 질문에 완전히 적용되지 않습니다!
Dave C

답변:


277

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

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

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

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


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

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


5
타입 정의를 완전히 버리고 싶지는 않을 것입니다. 해당 필드에 액세스하는이 유형에 다른 메소드를 작성하려는 경우와 같이 귀찮게됩니다. 중간체를 사용하는 map[string]interface{}것은 의미가 있지만 유형 정의를 버릴 필요는 없습니다.
jorelli

1
다른 답변은이 질문에 대한 실제 답변입니다.
Jacob

1
삭제의 가능한 단점은 때로는 구조체 (맵)의 여러 json보기를 지원하려고 할 수 있다는 것입니다. 예를 들어, 민감한 필드가없는 클라이언트의 경우 json보기 및 민감한 필드가있는 데이터베이스의 경우 json보기입니다. 다행히도 여전히 구조체를 사용하는 것이 가능합니다. 내 대답을 살펴보십시오.
Adam Kurkiewicz

이것은 단지 특정 필요 Id하지만 전체 json 구조체를 반환하지 않기 때문에 저에게 효과적입니다 . 감사합니다!
Louie Miranda

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

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


14
OP가 API에 대한 쿼리 문자열 항목을 기반으로 출력 필드를 동적으로 제어하고 싶다고 말했기 때문에 @Jacob에 동의하지 않습니다. 예를 들어, API 호출자가 산업 및 국가 만 요청하는 경우 나머지를 제거해야합니다. 이것이 "틱한"답변이이 질문에 대한 답변으로 표시되는 이유입니다. 이 투표는 정답을 절대로 사용할 수없는 모든 json-marshaler 필드에 표시합니다. 당신이 그것을 동적으로 원한다면, 틱 된 답변이 답변입니다.
eduncan911

12
이것은 검색에서 끝나는 대부분의 사람들이 원하는 답변이지만 질문에 대한 답변은 아닙니다.
Filip Haglund

5
이미 언급했듯이 OP는 DTO를 동적으로 형성하는 방법을 요구했습니다.
codepushr

54

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

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

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


3
+1 완전히 동의합니다. 나는 내장 된 마샬 러들과 함께이 규칙 / 트릭을 항상 사용한다 (그리고이 규칙을 기반으로 CSV 리더 / 라이터를 구축하기도했다! 그러면 OP는 단순히 * Country 값을 nil로 설정할 수 없으므로 생략됩니다. 그리고 멋진 y 타입의 play.golang도 제공 했다니 놀랍습니다.
eduncan911

2
물론이 방법에는 반사가 필요합니다. stdlib의 json-to-struct 마샬링은 항상 반사를 사용합니다 (실제로는 항상 반사주기, 맵 또는 구조체 등을 사용합니다).
mna

예. 그러나 인터페이스를 사용한 추가 반영이 필요하지 않으며 다른 답변이 권장합니다.
Druska

14

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


생각해 보면 selectfields 패턴을 모든 유형과 태그 키로 합리적으로 일반화 할 수 있습니다. SearchResult 정의 또는 json 키에만 해당되는 것은 없습니다.
jorelli

반성에서 벗어나려고하지만 유형 정보를 꽤 잘 저장합니다 ... validate () 메소드의 if / else 태그보다 구조가 어떻게 보이는지 문서화하는 것이 좋습니다 ( 하나)가
아크 타우

7

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

7

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

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

}

많이 드세요!


경고! includeFields에 실제 필드와 일치하지 않는 필드 이름이 포함되어 있으면 유효하지 않은 json이 표시됩니다. 경고를 받았습니다.
Adam Kurkiewicz

5

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


이것은 OP 질문과 유스 케이스에 대한 가장 정답입니다.
user1943442

2
@ user1943442는 아닙니다. 영업 이익은 명시 적으로 "omitempty는"적용 할 이유를 언급하고있다.
Dave C

2

나는 또한이 문제에 직면했다. 처음에는 http 핸들러에서 응답을 전문화하고 싶었다. 첫 번째 접근 방식은 구조체의 정보를 다른 구조체에 복사 한 다음 두 번째 구조체를 마샬링하는 패키지를 만드는 것입니다. 리플렉션을 사용하여 해당 패키지를 수행 했으므로 해당 접근 방식을 좋아하지 않았으며 동적이지 않았습니다.

그래서 이것을 위해 encoding / json 패키지를 수정하기로 결정했습니다. 기능 Marshal, MarshalIndent(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 인터페이스도 지원하는 것이 더 좋습니다.
huggie

1

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

https://github.com/tuvistavie/structomap


이제 내 레시피의 코드 스 니펫을 사용하여 쉽게 할 수 있습니다.
Adam Kurkiewicz

이 코드 조각은 라이브러리의 하위 집합이지만 a를 반환하는 데있어 주요한 문제 []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

일부 필드를 무시하여 구조체를 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


0

다음은 구조를 정의한 방법입니다.

type User struct {
    Username string  `json:"username" bson:"username"`
    Email    string  `json:"email" bson:"email"`
    Password *string `json:"password,omitempty" bson:"password"`
    FullName string  `json:"fullname" bson:"fullname"`
}

그리고 내 기능 안에서 user.Password = nil마샬링되지 않도록 설정 했습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.