Удаление полей из структуры или их скрытие в JSON Response

Я создал API в Go, который после вызова выполняет запрос, создает экземпляр структуры и затем кодирует эту структуру как JSON перед отправкой обратно вызывающему. Теперь я хочу, чтобы вызывающий мог выбрать конкретные поля, которые они хотели бы вернуть, передав параметр "поля" GET.

Это означает, что в зависимости от значений полей (s) моя структура изменится. Есть ли способ удалить поля из структуры? Или, по крайней мере, скрывать их в ответ 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)

Ответ 1

EDIT: Я заметил несколько downvotes и еще раз посмотрел на этот Q & A. Большинство людей, похоже, пропустили, что ОП попросил, чтобы поля были динамически выбраны на основе списка полей, предоставленных вызывающим абонентом. Вы не можете сделать это со статически определенным тегом struct json.

Если вы хотите всегда пропустить поле для json-encode, тогда, конечно, используйте json:"-", чтобы игнорировать это поле (также обратите внимание, что это не требуется, если ваше поле не экспортировано - эти поля всегда игнорируются json encoder). Но это не вопрос ОП.

Чтобы процитировать комментарий к ответе json:"-":

Этот [ответ json:"-"] - это ответ, который большинство людей, попадающих сюда из поиска, захотят, но это не ответ на вопрос.


Я использовал бы интерфейс map [string] {} вместо структуры в этом случае. Вы можете легко удалить поля, вызвав delete встроенный на карте для удаляемых полей.

То есть, если вы не можете запрашивать только запрашиваемые поля в первую очередь.

Ответ 2

используйте `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

Ответ 3

Другой способ сделать это - создать структуру указателей с тегом ,omitempty. Если указатели nil, поля не будут отмечены маркерами.

Этот метод не требует дополнительного отражения или неэффективного использования карт.

Тот же пример, что и jorelli, используя этот метод: http://play.golang.org/p/JJNa0m2_nw

Ответ 4

Вы можете использовать пакет 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

Ответ 5

Возьмите три ингредиента:

  1. reflect пакет, чтобы зациклить все поля структуры.

  2. Оператор if чтобы забрать поля, которые вы хотите Marshal, и

  3. Пакет encoding/json для Marshal по вашему вкусу.

Приготовление:

  1. Смешайте их в хорошей пропорции. Используйте reflect.TypeOf(your_struct).Field(i).Name() чтобы получить имя i го поля your_struct.

  2. Используйте reflect.ValueOf(your_struct).Field(i) чтобы получить представление типа Value i го поля your_struct.

  3. Используйте fieldValue.Interface() чтобы получить фактическое значение (преобразованное в интерфейс типа {}) поля fieldValue типа Value (обратите внимание на использование скобок - метод Interface() создает 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))
    }

}

Приятного аппетита!

Ответ 6

Я только что опубликовал шериф, который преобразует структуры в карту на основе тегов, помеченных на полях структуры. Затем вы можете выполнить маршал (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)
}

Ответ 7

Вы можете использовать атрибут tagging "omitifempty" или сделать необязательные указатели полей и оставить те, которые вы хотите пропустить, неинициализированные.

Ответ 8

Вопрос сейчас немного стар, но я столкнулся с той же проблемой некоторое время назад, и, поскольку я не нашел простого способа сделать это, я построил библиотеку, соответствующую этой цели. Это позволяет легко генерировать map[string]interface{} из статической структуры.

https://github.com/tuvistavie/structomap

Ответ 9

У меня не было той же проблемы, но похожей. Приведенный ниже код решает и вашу проблему, конечно, если вы не против проблемы производительности. Перед внедрением такого решения в вашей системе, я рекомендую вам изменить структуру, если вы можете. Отправка ответа переменной структуры является чрезмерной. Я полагаю, что структура ответа представляет собой контракт между запросом и ресурсом, и это не должно быть зависимым запросом (вы можете сделать ненужные поля пустыми, я так понимаю). В некоторых случаях нам приходится реализовывать этот дизайн, если вы считаете, что именно в этих случаях вот ссылка на игру и код, который я использую.

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

}

Ответ 11

Я также столкнулся с этой проблемой, сначала я просто хотел специализировать ответы в моем 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)
}