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)
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.
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"`
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
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
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:
El paquete
reflect
para hacer un bucle sobre todos los campos de una estructura.-
Una instrucción
if
para recoger los campos que deseaMarshal
, y El paquete
encoding/json
aMarshal
los campos de su gusto.
Preparación:
Mezclarlos en una buena proporción. Use
reflect.TypeOf(your_struct).Field(i).Name()
para obtener un nombre deli
campo deyour_struct
.Utilice
reflect.ValueOf(your_struct).Field(i)
para obtener un tipoValue
representación de uni
ésimo campo deyour_struct
.Use
fieldValue.Interface()
para recuperar el valor real (actualizado a type interfaz{}) delfieldValue
de typeValue
(observe el uso de corchetes - el método Interfaz () produceinterface{}
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!
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)
}
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.
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.
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))
}
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