Como não empacotar uma estrutura vazia em JSON com Go?


88

Eu tenho uma estrutura como esta:

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

Mas mesmo se a instância de MyStruct estiver totalmente vazia (ou seja, todos os valores são padrão), ela está sendo serializada como:

"data":{}

Eu sei que os documentos de codificação / json especificam que os campos "vazios" são:

falso, 0, qualquer ponteiro nulo ou valor de interface e qualquer matriz, fatia, mapa ou string de comprimento zero

mas sem considerar uma estrutura com todos os valores vazios / padrão. Todos os seus campos também são marcados com omitempty, mas isso não tem efeito.

Como posso fazer com que o pacote JSON não empacote meu campo que é uma estrutura vazia?

Respostas:


137

Como dizem os documentos, "qualquer ponteiro nulo". - torna a estrutura um ponteiro. Os ponteiros têm valores óbvios "vazio": nil.

Fix - defina o tipo com um campo de ponteiro de estrutura :

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

Então, um valor como este:

result := Result{}

Organizará como:

{}

Explicação: Observe o *MyStructem nossa definição de tipo. A serialização JSON não se importa se é um ponteiro ou não - isso é um detalhe de tempo de execução. Portanto, transformar campos de estrutura em ponteiros só tem implicações para a compilação e o tempo de execução).

Apenas observe que se você alterar o tipo de campo de MyStructpara *MyStruct, precisará de ponteiros para valores de estrutura para preenchê-lo, como:

Data: &MyStruct{ /* values */ }

2
Deus te abençoe Matt, isso é o que eu estava procurando
Venkata SSKM Chaitanya

@Matt, você tem certeza de que &MyStruct{ /* values */ }conta como um ponteiro nulo? O valor não é nulo.
Shuzheng

@Matt É possível tornar esse comportamento padrão? Quero omitir sempre o vazio. (basicamente não usar a tag em todos os campos de todos os structs)
Mohit Singh

17

Como @chakrit mencionado em um comentário, você não pode chegar a este trabalho através da implementação json.Marshalerde MyStruct, e implementar uma função de triagem JSON personalizado em cada struct que usa-lo pode ser muito mais trabalho. Realmente depende do seu caso de uso se vale a pena o trabalho extra ou se você está preparado para viver com estruturas vazias em seu JSON, mas aqui está o padrão 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
}

Se você tem structs enormes com muitos campos, isso pode se tornar tedioso, especialmente alterar a implementação de um struct mais tarde, mas sem reescrever todo o jsonpacote para atender às suas necessidades (não é uma boa ideia), essa é praticamente a única maneira que consigo pensar em conseguir isso é feito enquanto mantém um não-ponteiro MyStructlá.

Além disso, você não precisa usar estruturas embutidas, você pode criar estruturas nomeadas. Eu uso LiteIDE com autocompletar de código, então eu prefiro inline para evitar confusão.


9

Dataé uma estrutura inicializada, portanto, não é considerada vazia porque encoding/jsonolha apenas para o valor imediato, não os campos dentro da estrutura.

Infelizmente, retornar nilde json.Marhsleratualmente não funciona:

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

Você poderia dar Resultum empacotador também, mas não vale a pena o esforço.

A única opção, como sugere Matt, é fazer Dataum ponteiro e definir o valor como nil.


1
Não vejo por encoding/json que não posso verificar os campos filho da estrutura. Não seria muito eficiente, sim. Mas certamente não é impossível.
nemo

@nemo Eu entendo seu ponto, mudei o texto. Não funciona porque não seria eficiente. No json.Marshalerentanto, isso pode ser feito caso a caso.
Lucas

2
É não possível decidir wether ou não MyStructestá vazio , implementando um json.Marshalerem MyStructsi. Prova: play.golang.org/p/UEC8A3JGvx
chakrit

Para fazer isso, você terá que implementar json.Marshalerno Resultpróprio tipo de conteúdo , o que pode ser muito inconveniente.
chakrit

3

Há uma excelente proposta da Golang para esse recurso que está ativa há mais de 4 anos, portanto, neste ponto, é seguro presumir que ele não fará parte da biblioteca padrão tão cedo. Como @Matt apontou, a abordagem tradicional é converter os structs em ponteiros para structs . Se essa abordagem for inviável (ou impraticável), uma alternativa é usar um codificador json alternativo que suporte a omissão de estruturas de valor zero .

Eu criei um espelho da biblioteca Golang json ( clarketm / json ) com suporte adicionado para omitir estruturas de valor zero quando a omitemptytag é aplicada. Essa biblioteca detecta o zeroness de maneira semelhante ao popular codificador YAML go-yaml , verificando recursivamente os campos de estrutura pública .

por exemplo

$ 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"
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.