¿Cómo no agrupar una estructura vacía en JSON con Go?

88

Tengo una estructura como esta:

type Result struct {
    Data       MyStruct  `json:"data,omitempty"`
    Status     string    `json:"status,omitempty"`
    Reason     string    `json:"reason,omitempty"`
}

Pero incluso si la instancia de MyStruct está completamente vacía (es decir, todos los valores son predeterminados), se serializa como:

"data":{}

Sé que los documentos de codificación / json especifican que los campos "vacíos" son:

falso, 0, cualquier puntero nulo o valor de interfaz, y cualquier matriz, sector, mapa o cadena de longitud cero

pero sin tener en cuenta una estructura con todos los valores vacíos / predeterminados. Todos sus campos también están etiquetados con omitempty, pero esto no tiene ningún efecto.

¿Cómo puedo hacer que el paquete JSON no clasifique mi campo que es una estructura vacía?

Mate
fuente

Respuestas:

137

Como dicen los documentos, "cualquier puntero nulo". - convierte la estructura en un puntero. Los punteros tienen valores obvios "vacías": nil.

Arreglar: defina el tipo con un campo de puntero de estructura :

type Result struct {
    Data       *MyStruct `json:"data,omitempty"`
    Status     string    `json:"status,omitempty"`
    Reason     string    `json:"reason,omitempty"`
}

Entonces un valor como este:

result := Result{}

Will mariscal como:

{}

Explicación: observe el *MyStructen nuestra definición de tipo. A la serialización JSON no le importa si es un puntero o no, ese es un detalle del tiempo de ejecución. Por lo tanto, convertir campos de estructura en punteros solo tiene implicaciones para la compilación y el tiempo de ejecución).

Solo tenga en cuenta que si cambia el tipo de campo de MyStructa *MyStruct, necesitará punteros para estructurar valores para completarlo, así:

Data: &MyStruct{ /* values */ }
Mate
fuente
2
Bendito seas Matt, esto es lo que estaba buscando
Venkata SSKM Chaitanya
@ Matt, ¿estás seguro de que eso &MyStruct{ /* values */ }cuenta como un puntero nulo? El valor no es nulo.
Shuzheng
@Matt ¿Es posible realizar este comportamiento predeterminado? Quiero omitevacío siempre. (básicamente, no use la etiqueta en todos y cada uno de los campos de todas las estructuras)
Mohit Singh
17

Como @chakrit mencionado en un comentario, usted no puede conseguir que esto funcione mediante la implementación json.Marshalerde MyStruct, y la implementación de una función de clasificación JSON personalizado en cada estructura que los usos que pueden ser mucho más trabajo. Realmente depende de su caso de uso en cuanto a si vale la pena el trabajo adicional o si está preparado para vivir con estructuras vacías en su JSON, pero aquí está el patrón que uso aplicado a Result:

type Result struct {
    Data       MyStruct
    Status     string   
    Reason     string    
}

func (r Result) MarshalJSON() ([]byte, error) {
    return json.Marshal(struct {
        Data     *MyStruct   `json:"data,omitempty"`
        Status   string      `json:"status,omitempty"`
        Reason   string      `json:"reason,omitempty"`
    }{
        Data:   &r.Data,
        Status: r.Status,
        Reason: r.Reason,
    })        
}

func (r *Result) UnmarshalJSON(b []byte) error {
    decoded := new(struct {
        Data     *MyStruct   `json:"data,omitempty"`
        Status   string      `json:"status,omitempty"`
        Reason   string      `json:"reason,omitempty"`
    })
    err := json.Unmarshal(b, decoded)
    if err == nil {
        r.Data = decoded.Data
        r.Status = decoded.Status
        r.Reason = decoded.Reason
    }
    return err
}

Si tiene estructuras enormes con muchos campos, esto puede volverse tedioso, especialmente cambiar la implementación de una estructura más adelante, pero a menos que vuelva a escribir todo el jsonpaquete para satisfacer sus necesidades (no es una buena idea), esta es prácticamente la única forma en que puedo pensar en obtener esto se hace mientras se mantiene un no puntero MyStructallí.

Además, no tiene que usar estructuras en línea, puede crear estructuras con nombre. Sin embargo, uso LiteIDE con finalización de código, por lo que prefiero en línea para evitar el desorden.

Leylandski
fuente
9

Dataes una estructura inicializada, por lo que no se considera vacía porque encoding/jsonsolo mira el valor inmediato, no los campos dentro de la estructura.

Desafortunadamente, regresar nilde json.Marhsleractualmente no funciona:

func (_ MyStruct) MarshalJSON() ([]byte, error) {
    if empty {
        return nil, nil // unexpected end of JSON input
    }
    // ...
}

También podrías dar Resultun asesor, pero no vale la pena el esfuerzo.

La única opción, como sugiere Matt, es hacer Dataun puntero y establecer el valor en nil.

Luke
fuente
1
No veo por qué encoding/json no puedo verificar los campos secundarios de la estructura. No sería muy eficiente, sí. Pero ciertamente no es imposible.
nemo
@nemo veo tu punto, cambié la redacción. No lo hace porque no sería eficiente. Sin json.Marshalerembargo, se puede hacer caso por caso.
Lucas
2
Es no es posible decidir tiempo o no MyStructestá vacío mediante la implementación de un json.Marshalersobre MyStructsí mismo. Prueba: play.golang.org/p/UEC8A3JGvx
chakrit
Para hacer eso, tendría que implementar json.Marshaleren el Resulttipo contenedor en sí, lo que podría ser muy inconveniente.
chakrit
3

Existe una excelente propuesta de Golang para esta función que ha estado activa durante más de 4 años, por lo que en este punto, es seguro asumir que no se incluirá en la biblioteca estándar en el corto plazo. Como señaló @Matt, el enfoque tradicional es convertir las estructuras en punteros a estructuras . Si este enfoque no es factible (o impráctico), entonces una alternativa es usar un codificador json alternativo que admita la omisión de estructuras de valor cero .

Creé un espejo de la biblioteca Golang json ( clarketm / json ) con soporte adicional para omitir estructuras de valor cero cuando omitemptyse aplica la etiqueta. Esta biblioteca detecta zeroness de manera similar al popular codificador YAML go-yaml al verificar de forma recursiva los campos de estructura públicos .

p.ej

$ go get -u "github.com/clarketm/json"
import (
    "fmt"
    "github.com/clarketm/json" // drop-in replacement for `encoding/json`
)

type Result struct {
    Data   MyStruct `json:"data,omitempty"`
    Status string   `json:"status,omitempty"`
    Reason string   `json:"reason,omitempty"`
}

j, _ := json.Marshal(&Result{
    Status: "204",
    Reason: "No Content",
})

fmt.Println(string(j))
// Note: `data` is omitted from the resultant json.
{
  "status": "204"
  "reason": "No Content"
}
Travis Clarke
fuente