Làm cách nào để không điều chỉnh một cấu trúc trống thành JSON với Go?


88

Tôi có một cấu trúc như thế này:

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

Nhưng ngay cả khi phiên bản của MyStruct hoàn toàn trống (nghĩa là, tất cả các giá trị đều là mặc định), nó vẫn được tuần tự hóa thành:

"data":{}

Tôi biết rằng tài liệu mã hóa / json chỉ định rằng các trường "trống" là:

false, 0, bất kỳ con trỏ nil hoặc giá trị giao diện nào và bất kỳ mảng, lát cắt, bản đồ hoặc chuỗi nào có độ dài bằng 0

nhưng không xem xét cấu trúc có tất cả các giá trị trống / mặc định. Tất cả các trường của nó cũng được gắn thẻ omitempty, nhưng điều này không có tác dụng.

Làm cách nào tôi có thể tải gói JSON để không điều khiển trường của tôi là một cấu trúc trống?

Câu trả lời:


137

Như các tài liệu nói, "bất kỳ con trỏ nil nào." - biến struct thành một con trỏ. Con trỏ có rõ ràng giá trị "trống rỗng": nil.

Khắc phục - xác định kiểu với trường con trỏ cấu trúc :

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

Sau đó, một giá trị như thế này:

result := Result{}

Sẽ soái như:

{}

Giải thích: Lưu ý *MyStructtrong định nghĩa kiểu của chúng tôi. Tuần tự hóa JSON không quan tâm liệu nó có phải là con trỏ hay không - đó là chi tiết thời gian chạy. Vì vậy, việc biến các trường struct thành con trỏ chỉ có ý nghĩa đối với việc biên dịch và thời gian chạy).

Chỉ cần lưu ý rằng nếu bạn thay đổi loại trường từ MyStructthành *MyStruct, bạn sẽ cần con trỏ đến giá trị cấu trúc để điền nó, như sau:

Data: &MyStruct{ /* values */ }

2
Bless bạn Matt, Đây là những gì tôi đang tìm kiếm
Venkata SSKM Chaitanya

@Matt, bạn có chắc điều đó &MyStruct{ /* values */ }được tính là con trỏ nil không? Giá trị không phải là con số không.
Shuzheng

@Matt Có thể thực hiện hành vi mặc định này không? Tôi luôn muốn omitempty. (về cơ bản không sử dụng thẻ trong mỗi và mọi trường của tất cả các cấu trúc)
Mohit Singh

17

Như @chakrit đã đề cập trong một nhận xét, bạn không thể làm cho điều này hoạt động bằng cách triển khai json.Marshalertrên MyStructvà việc triển khai một chức năng điều phối JSON tùy chỉnh trên mọi cấu trúc sử dụng nó có thể tốn nhiều công sức hơn. Nó thực sự phụ thuộc vào trường hợp sử dụng của bạn về việc liệu nó có đáng để làm thêm hay không hoặc liệu bạn có sẵn sàng sống với các cấu trúc trống trong JSON của mình hay không, nhưng đây là mẫu tôi sử dụng áp dụng cho 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
}

Nếu bạn có cấu trúc khổng lồ với nhiều trường, điều này có thể trở nên tẻ nhạt, đặc biệt là việc thay đổi cách triển khai của cấu trúc sau này, nhưng thiếu việc viết lại toàn bộ jsongói cho phù hợp với nhu cầu của bạn (không phải là một ý kiến ​​hay), đây là cách duy nhất tôi có thể nghĩ đến điều này được thực hiện trong khi vẫn giữ một con trỏ không MyStructở đó.

Ngoài ra, bạn không cần phải sử dụng cấu trúc nội dòng, bạn có thể tạo những cấu trúc được đặt tên. Tuy nhiên, tôi sử dụng LiteIDE với mã hoàn thành, vì vậy tôi thích nội dòng hơn để tránh lộn xộn.


9

Datalà một cấu trúc được khởi tạo, vì vậy nó không được coi là trống vì encoding/jsonchỉ xem xét giá trị ngay lập tức chứ không phải các trường bên trong cấu trúc.

Rất tiếc, việc quay lại niltừ json.Marhslerhiện tại không hoạt động:

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

Bạn cũng có thể cung cấp cho Resultmột người điều phối, nhưng nó không đáng để nỗ lực.

Lựa chọn duy nhất, như Matt gợi ý, là tạo Datamột con trỏ và đặt giá trị thành nil.


1
Tôi không hiểu tại sao encoding/json không thể kiểm tra các trường con của struct. Nó sẽ không hiệu quả lắm, vâng. Nhưng chắc chắn không phải là không thể.
nemo

@nemo Tôi hiểu ý bạn, tôi đã thay đổi từ ngữ. Nó không làm điều đó vì nó sẽ không hiệu quả. Tuy nhiên, nó có thể được thực hiện với json.Marshalertừng trường hợp cụ thể.
Luke

2
Nó là không thể quyết định thời tiết hay không MyStructlà trống bằng cách thực hiện một json.Marshalertrên MyStructchính nó. Chứng minh: play.golang.org/p/UEC8A3JGvx
chakrit

Để làm điều đó, bạn phải triển khai json.Marshalertrên Resultchính kiểu chứa , điều này có thể rất bất tiện.
chakrit

3

Có một đề xuất nổi bật của Golang cho tính năng này đã hoạt động hơn 4 năm, vì vậy tại thời điểm này, có thể yên tâm rằng nó sẽ không sớm được đưa vào thư viện tiêu chuẩn. Như @Matt đã chỉ ra, cách tiếp cận truyền thống là chuyển đổi cấu trúc thành con trỏ-cấu trúc . Nếu cách tiếp cận này không khả thi (hoặc không thực tế), thì một giải pháp thay thế là sử dụng bộ mã hóa json thay thế hỗ trợ bỏ qua cấu trúc giá trị 0 .

Tôi đã tạo một bản sao của thư viện Golang json ( clarketm / json ) với hỗ trợ bổ sung để bỏ qua các cấu trúc giá trị 0 khi omitemptythẻ được áp dụng. Đây thư viện Phát hiện zeroness trong một cách tương tự như phổ biến YAML encoder go-yaml bằng cách đệ quy kiểm tra công lĩnh vực struct .

ví dụ

$ 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"
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.