Як не вкласти порожню структуру в JSON за допомогою Go?


89

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

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

Але навіть якщо екземпляр MyStruct повністю порожній (мається на увазі, всі значення за замовчуванням), він серіалізується як:

"data":{}

Я знаю, що документи кодування / json вказують, що поля "порожні":

false, 0, будь-який нульовий покажчик або значення інтерфейсу, а також будь-який масив, зріз, карта або рядок довжиною нуль

але без урахування структури зі всіма порожніми / значеннями за замовчуванням. Усі його поля також позначені тегами omitempty, але це ніяк не впливає.

Як я можу змусити пакет JSON не маршувати моє поле, яке є порожньою структурою?

Відповіді:


138

Як сказано в документах, "будь-який нульовий покажчик". - зробити структуру вказівником. Покажчики мають очевидні «порожні» значення: nil.

Виправлено - визначити тип за допомогою поля вказівника struct :

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
chakrit

Для цього вам доведеться реалізувати json.Marshalerна самому Resultтипі, що містить , що може бути дуже незручно.
chakrit

3

Існує видатна пропозиція Golang щодо цієї функції, яка діє вже понад 4 роки, тому на даний момент можна впевнено припустити, що вона не потрапить у стандартну бібліотеку найближчим часом. Як зазначав @Matt, традиційний підхід полягає у перетворенні структур на покажчики на структури . Якщо такий підхід неможливий (або непрактичний), тоді альтернативою є використання альтернативного кодера json, який підтримує відсутність нульових структур .

Я створив дзеркало json-бібліотеки Golang ( 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"
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.