Как не маршалировать пустую структуру в JSON с помощью Go?

88

У меня есть такая структура:

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

Но даже если экземпляр MyStruct полностью пуст (то есть все значения по умолчанию), он сериализуется как:

"data":{}

Я знаю, что в документации по кодировке / json указано, что "пустыми" полями являются:

false, 0, любой указатель nil или значение интерфейса, а также любой массив, фрагмент, карта или строка нулевой длины

но без учета структуры со всеми пустыми значениями / значениями по умолчанию. Все его поля также помечены omitempty, но это не влияет.

Как я могу заставить пакет JSON не маршалировать мое поле, которое является пустой структурой?

Мэтт
источник

Ответы:

137

Как говорится в документации, «любой нулевой указатель». - сделать структуру указателем. Указатели имеют очевидные «пустые» значения: nil.

Исправить - определить тип с полем указателя структуры :

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

Тогда значение вроде этого:

result := Result{}

Маршалируется как:

{}

Пояснение: обратите внимание на *MyStruct в нашем определении типа. Сериализации JSON не важно, указатель это или нет - это деталь времени выполнения. Таким образом, превращение полей структуры в указатели имеет значение только для компиляции и выполнения).

Просто обратите внимание, что если вы измените тип поля с MyStructна *MyStruct, вам потребуются указатели на структуру значений для его заполнения, например:

Data: &MyStruct{ /* values */ }
Мэтт
источник
2
Благослови тебя, Мэтт, это то, что я искал
Венката ССКМ Чайтанья
@Matt, вы уверены, что это &MyStruct{ /* values */ }считается нулевым указателем? Значение не равно нулю.
Шучжэн,
@Matt Можно ли сделать это по умолчанию? Я хочу всегда опускать пустоту. (в основном не использовать тег в каждом поле всех структур)
Мохит Сингх
17

Как @chakrit упоминались в комментариях, вы не можете получить эту работу по реализации json.Marshalerна MyStructи реализации функции сортировочной пользовательских JSON на каждую структуру , которая использует это может быть намного больше работы. Это действительно зависит от вашего варианта использования относительно того, стоит ли дополнительная работа или вы готовы жить с пустыми структурами в своем JSON, но вот шаблон, который я использую, применяется к 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
}

Если у вас есть огромные структуры с множеством полей, это может стать утомительным, особенно изменение реализации структуры позже, но если jsonне считать переписывания всего пакета в соответствии с вашими потребностями (не очень хорошая идея), это в значительной степени единственный способ получить это сделано при сохранении не указателяMyStruct .

Кроме того, вам не обязательно использовать встроенные структуры, вы можете создавать именованные. Однако я использую LiteIDE с автозавершением кода, поэтому предпочитаю встроенный, чтобы избежать беспорядка.

Лейландски
источник
9

Data - это инициализированная структура, поэтому она не считается пустой, потому что encoding/json смотрит только на непосредственное значение, а не на поля внутри структуры.

К сожалению, возврат nilиз json.Marhslerтекущего состояния не работает:

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

Вы также можете дать Resultмаршалер, но это того не стоит.

Единственный вариант, как предлагает Мэтт, - создать Dataуказатель и установить значение на nil.

Люк
источник
1
Я не понимаю, почему encoding/json не могу проверить дочерние поля структуры. Да, это было бы не очень эффективно. Но это, конечно, не невозможно.
nemo
@nemo Я понял вашу точку зрения, я изменил формулировку. Он этого не делает, потому что это было бы неэффективно. Однако это можно сделать json.Marshalerв индивидуальном порядке.
Люк
2
Это не возможно решить кастрированный баран или не MyStructпуст , реализовав json.Marshalerна MyStructсебя. Доказательство: play.golang.org/p/UEC8A3JGvx
чакрит
Для этого вам придется реализовать json.Marshalerсам содержащий Resultтип, что может быть очень неудобно.
чакрит
3

Есть замечательное предложение Golang для этой функции, которое действует уже более 4 лет, так что на данный момент можно с уверенностью предположить, что в ближайшее время она не войдет в стандартную библиотеку. Как отметил @Matt, традиционный подход заключается в преобразовании структур в указатели на структуры . Если такой подход невозможен (или непрактичен), то альтернативой является использование альтернативного кодировщика json, который поддерживает пропуск структур с нулевым значением .

Я создал зеркало библиотеки Golang json ( clarketm / json ) с добавленной поддержкой для исключения структур с нулевым значением при применении omitemptyтега. Эта библиотека обнаруживает нули аналогично популярному кодировщику YAML go-yaml путем рекурсивной проверки полей общедоступной структуры .

например

$ 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"
}
Трэвис Кларк
источник