Eliminar campos de la estructura u ocultarlos en la respuesta JSON


He creado una API en Go que, al ser llamada, realiza una consulta, crea una instancia de una estructura y luego codifica esa estructura como JSON antes de enviarla de vuelta al llamador. Ahora me gustaría permitir que la persona que llama pueda seleccionar los campos específicos que le gustaría devolver al pasar un parámetro GET "fields".

Esto significa que dependiendo del valor de los campos, mi estructura cambiaría. Hay alguna forma de eliminar campos de una estructura? O al menos ocultarlos en la respuesta JSON dinámicamente? (Nota: A veces tengo valores vacíos por lo que la etiqueta JSON omitEmpty no funcionará aquí) Si ninguno de estos son posibles, ¿hay alguna sugerencia sobre una mejor manera de manejar esto? Gracias de antemano.

Una versión más pequeña de las estructuras que estoy usando están a continuación:

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

Luego codifico y emito la respuesta de la siguiente manera:

err := json.NewEncoder(c.ResponseWriter).Encode(&msg)
 103
Author: Flimzy, 2013-06-25

9 answers

EDITAR: Noté algunos votos negativos y eché otro vistazo a esta Pregunta y Respuesta.La mayoría de la gente parece extrañar que el OP pidiera que los campos se seleccionaran dinámicamente en función de la lista de campos proporcionada por el llamante. No se puede hacer esto con la etiqueta de estructura json definida estáticamente.

Si lo que desea es siempre omitir un campo a json-encode, entonces, por supuesto, use json:"-" para ignorar el campo (también tenga en cuenta que esto es no obligatorio si su campo no se ignorado por el codificador json). Pero esa no es la pregunta de la OP.

Para citar el comentario sobre la respuesta json:"-":

Esta [la respuesta json:"-"] es la respuesta que la mayoría de la gente que termina aquí de buscar querría, pero no es la respuesta a la pregunta.


Usaría una interfaz map[string] {} en lugar de una estructura en este caso. Puede eliminar campos fácilmente llamando al elemento integrado delete en el mapa para que los campos se eliminen.

Es decir, si no puede consultar solo para los campos solicitados en primer lugar.

 157
Author: mna,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-03-29 18:46:38

Use '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

 121
Author: GivenJazz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-03-28 07:46:31

Otra forma de hacer esto es tener una estructura de punteros con el ,omitempty etiqueta. Si los punteros son nil, los campos no pueden Calcularse.

Este método no requerirá reflexión adicional ni uso ineficiente de mapas.

El mismo ejemplo que jorelli usando este método: http://play.golang.org/p/JJNa0m2_nw

 37
Author: Druska,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-03-30 17:08:06

Puede usar el paquete reflect para seleccionar los campos que desee reflejando en las etiquetas de campo y seleccionando los valores de la etiqueta json. Defina un método en su tipo searchResults que seleccione los campos que desee y los devuelva como map[string]interface{}, y luego marche que en lugar de la estructura searchResults en sí. Aquí hay un ejemplo de cómo podría definir ese método:

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
}

Y aquí hay una solución ejecutable que muestra cómo llamaría a este método y organizaría su selección: http://play.golang.org/p/1K9xjQRnO8

 12
Author: jorelli,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-06-26 14:40:28

Tomar tres ingredientes:

  1. El paquete reflect para hacer un bucle sobre todos los campos de una estructura.

  2. Una instrucción if para recoger los campos que desea Marshal, y

  3. El paquete encoding/json a Marshal los campos de su gusto.

Preparación:

  1. Mezclarlos en una buena proporción. Use reflect.TypeOf(your_struct).Field(i).Name() para obtener un nombre del i campo de your_struct.

  2. Utilice reflect.ValueOf(your_struct).Field(i) para obtener un tipo Value representación de un i ésimo campo de your_struct.

  3. Use fieldValue.Interface() para recuperar el valor real (actualizado a type interfaz{}) del fieldValue de type Value (observe el uso de corchetes - el método Interfaz () produce interface{}

Si por suerte logra no quemar ningún transistor o disyuntor en el proceso, debería obtener algo como esto:

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
}

Sirviendo:

Servir con una estructura arbitraria y un map[string]bool de campos que desea include, for example

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

}

Bon Appetit!

 5
Author: Adam Kurkiewicz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-07-20 06:09:17

Acabo de publicar sheriff, que transforma estructuras en un mapa basado en etiquetas anotadas en los campos de estructura. A continuación, puede ordenar (JSON u otros) el mapa generado. Probablemente no te permita serializar solo el conjunto de campos que solicitó el llamante, pero imagino que usar un conjunto de grupos te permitiría cubrir la mayoría de los casos. El uso de grupos en lugar de los campos directamente probablemente también aumentaría la capacidad de caché.

Ejemplo:

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)
}
 3
Author: Michael Weibel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-26 18:58:26

Puede usar el atributo de etiquetado "omitifempty" o hacer punteros de campos opcionales y dejar aquellos que desea omitir sin inicializar.

 2
Author: deemok,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-06-26 07:46:16

La pregunta ahora es un poco vieja, pero me encontré con el mismo problema hace un rato, y como no encontré una manera fácil de hacer esto, construí una biblioteca cumpliendo este propósito. Permite generar fácilmente un map[string]interface{} a partir de una estructura estática.

Https://github.com/tuvistavie/structomap

 1
Author: Daniel Perez,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-06-03 05:02:46

No tenía el mismo problema, pero similar. El siguiente código también resuelve su problema, por supuesto, si no le importa el problema de rendimiento. Antes de implementar ese tipo de solución a su sistema, le recomiendo que rediseñe su estructura si puede. Enviar respuesta de estructura variable es sobreingeniería. Creo que una estructura de respuesta representa un contrato entre una solicitud y un recurso y no debería ser una solicitud dependiente.(puedes hacer que los campos no deseados sean nulos, yo sí). En algunos casos tenemos que implementar este diseño, si crees que estás en esos casos aquí está el enlace de reproducción y el código que uso.

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

}
 0
Author: RockOnGom,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-13 13:02:57