Удаление полей из структуры или скрытие их в ответе JSON
Я создал API в Go, который при вызове выполняет запрос, создает экземпляр структуры, а затем кодирует эту структуру как JSON перед отправкой обратно вызывающему объекту. Теперь я хотел бы позволить вызывающему абоненту выбрать конкретные поля, которые они хотели бы вернуть, передав параметр GET "fields".
Это означает, что в зависимости от значений полей моя структура изменится. Есть ли способ удалить поля из структуры? Или, по крайней мере, скрыть их в 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)
9 ответов:
EDIT: я заметил несколько downvotes и еще раз взглянул на этот Q&A. большинство людей, похоже, пропустили, что OP попросил Поля быть динамически выбрано на основе списка полей, предоставленных вызывающим абонентом. Вы не можете сделать это со статически определенным тегом JSON struct.
если ты хочешь всегда пропустить поле для JSON-кодирования, а затем, конечно, использовать
json:"-"игнорировать поле (также обратите внимание, что это не требуется, если ваше поле неэкспортируется - эти поля всегда игнорируется кодировщик JSON). Но это не вопрос ОП.чтобы процитировать комментарий на
json:"-"ответ:это
json:"-"ответ] это ответ, который большинство людей в конечном итоге здесь от поиска хотели бы, но это не ответ на вопрос.
в этом случае я бы использовал интерфейс map[string] {} вместо структуры. Вы можете легко удалить поля, вызвав
deleteвстроенный on карта для полей, которые нужно удалить.то есть, если вы не можете запрашивать только необходимые поля в первую очередь.
используйте `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"`
другой способ сделать это-иметь структуру указатели С
,omitemptyтег. Если указатели Нил, поля не будут упорядочены.этот метод не потребует дополнительного отражения или неэффективного использования карт.
тот же пример, что и jorelli, используя этот метод:http://play.golang.org/p/JJNa0m2_nw
можно использовать
reflectпакет для выбора полей, которые вы хотите, отражая на тегах поля и выбравjsonзначения тегов. Определите метод для вашего типа SearchResults, который выбирает нужные поля и возвращает их какmap[string]interface{}, а потом маршалом это вместо самой структуры SearchResults. Вот пример того, как вы можете определить этот метод: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
взять три ингредиента:
The
reflectпакет для циклического перебора всех полей структуры.An
ifзаявление, чтобы забрать поля, которые вы хотитеMarshalиThe
encoding/jsonпакетаMarshalполя по своему вкусу.Приготовление:
смешайте их в хорошей пропорции. Используйте
reflect.TypeOf(your_struct).Field(i).Name()получить имяiобластиyour_struct.использовать
reflect.ValueOf(your_struct).Field(i)чтобы получить типValueпредставлениеiобластиyour_struct.использовать
fieldValue.Interface()чтобы получить фактическое значение (upcasted to type interface {})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)) } }Приятного Аппетита!
Я только что опубликовал шериф, который преобразует структуры в карту на основе тегов, аннотированных в полях структуры. Затем вы можете маршалировать (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) }
вы можете использовать атрибут тегирования "omitifempty" или сделать необязательные указатели полей и оставить те, которые вы хотите пропустить, неинициализированными.
вопрос теперь немного устарел, но я столкнулся с той же проблемой некоторое время назад, и поскольку я не нашел простого способа сделать это, я построил библиотеку, выполняющую эту цель. Это позволяет легко генерировать
map[string]interface{}от статической структуры.
У меня не было такой же проблемы, но похоже. Ниже код решает вашу проблему тоже, конечно, если вы не возражаете проблемы с производительностью. Прежде чем внедрять такое решение в свою систему, я рекомендую вам перепроектировать свою структуру, если вы можете. Отправка ответа переменной структуры является чрезмерной инженерией. Я считаю, что структура ответа представляет собой контракт между запросом и ресурсом, и он не должен зависеть от запросов.(вы можете сделать ненужные поля 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)) }
Comments