구조체에서 필드 제거 또는 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)
}
세 가지 성분을 섭취하십시오.
reflect
구조체의 모든 필드를 반복 하는 패키지입니다.if
문은 당신이 원하는 필드를 데리러Marshal
하고,encoding/json
에 패키지Marshal
원하는대로의 분야.
예비:
좋은 비율로 혼합하십시오.
reflect.TypeOf(your_struct).Field(i).Name()
의i
th 필드 이름을 얻는 데 사용하십시오your_struct
.의 th 필드의
reflect.ValueOf(your_struct).Field(i)
형식Value
표현 을 얻는 데 사용 합니다 .i
your_struct
사용하여
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)
}
'IT박스' 카테고리의 다른 글
Postman으로 헤더에 JWT 토큰 보내기 (0) | 2020.06.09 |
---|---|
이미지를 ImageView에 맞추고 종횡비를 유지 한 다음 ImageView의 크기를 이미지 크기로 조정 하시겠습니까? (0) | 2020.06.09 |
Javascript에서 괄호 사이에 문자열을 가져 오는 정규식 (0) | 2020.06.09 |
문자열로 저장된 JavaScript 코드 실행 (0) | 2020.06.09 |
두 개의 필드로 파이썬리스트 정렬 (0) | 2020.06.09 |