Golang Marshal/Unmarshal JSON 具有导出和未导出的字段

Golang Marshal/Unmarshal JSON with both exported and un-exported fields

我见过很多 marshal/unmarshal 只包含未导出字段的结构的方法。但是我该如何处理混合字段呢?

给定一个结构:

type Test struct {
    fieldA string `json:"fieldA"`
    FieldB int    `json:"fieldB"`
    FieldC string `json:"fieldC"`
}

如何编写 MarshalJSON/UnmarshalJSON 函数以便将 fieldA 与 FieldB 和 FieldC 一起传输?

以下编译,但当我 运行 它时调用堆栈溢出。我的猜测是我正在递归编组对象,但我不确定在编码时如何保留导出和未导出的字段。

func (t *Test) MarshalJSON() ([]byte, error) {
    return json.Marshal(struct {
         *Test
         FieldA string `json:"fieldA"`
    }{
         t,
         t.fieldA,
    })
}

func (t *Test) UnmarshalJSON(b []byte) error {
    return json.Unmarshal(b, &t)
}

有办法吗?或者我应该重新考虑我的数据结构,也许只是导出字段?

注意: 我知道我可以手动完成每个字段,但我想尽可能避免这样做,以便更易于管理更新代码。

您可以创建一个特定的结构来处理 JSON 序列化消息:http://play.golang.org/p/d057T7qfVB

type Test struct {
    fieldA string
    FieldB int
    FieldC string
}

type TestJSON struct {
    FieldA string `json:"fieldA"`
    FieldB int    `json:"fieldB"`
    FieldC string `json:"fieldC"`
}

func (t *Test) MarshalJSON() ([]byte, error) {
    return json.Marshal(TestJSON{
        t.fieldA,
        t.FieldB,
        t.FieldC,
    })
}

func (t *Test) UnmarshalJSON(b []byte) error {
    temp := &TestJSON{}

    if err := json.Unmarshal(b, &temp); err != nil {
        return err
    }

    t.fieldA = temp.FieldA
    t.FieldB = temp.FieldB
    t.FieldC = temp.FieldC

    return nil
}

另一个解决方案完全可用,但需要更新 在许多地方,只要字段发生变化。这个解决方案更容易 维护。

通过使用类型别名和 结构嵌入,我们可以创建一个更干的解决方案。新的 字段自动按预期工作,除非它们未导出 或需要自定义格式。在这种情况下,最少量的工作 是必需的:只需在 *JSON 结构中列出特殊字段并且 在 MarshalJSONUnmarshalJSON.

中包含转换表达式
package main

/*

The Go JSON module can't not access unexported fields in a struct. So
how do you work with them?

This demonstrates the solution in http://choly.ca/post/go-json-marshalling/
where we have a 2nd struct that embeds the primary struct but adds
fields that will be used to expose the unexported fields.  We then write
MarshalJSON() and UnmarshalJSON() functions that do the right thing.

This also helps in situations where we have fields that require a custom
format only in JSON.
*/

import (
    "encoding/json"
    "fmt"
    "time"
)

// Cranberry stores data.
//  Visible: This field is exported and JSON displays it as usual.
//  invisible: This field is unexported but we want it to be included in JSON.
//  Custom: This field has a custom output format.  We store it as time.Time
//    but when it appears in JSON, it should be in Unix Epoch format.
type Cranberry struct {
    Visible   int       `json:"visible"`
    invisible int       // No tag here
    Custom    time.Time `json:"-"` // Don't output this field (we'll handle it in CranberryJSON).
}

// CranberryAlias is an alias of Cranberry. We use an alias because aliases
// are stripped of any functions and we need a struct without
// MarshalJSON/UnmarshalJSON defined, otherwise we'd get a recursive defintion.
type CranberryAlias Cranberry

// CranberryJSON represents out we represent Cranberry to the JSON package.
type CranberryJSON struct {
    *CranberryAlias       // All the exported fields.
    Invisible       int   `json:"invisible"`
    CustomUnixEpoch int64 `json:"epoch"`
    // FYI: The json tags "invisble" and "epoch" can be any valid JSON tag.
    // It is all a matter of how we want the JSON to be presented externally.
}

// MarshalJSON marshals a Cranberry. (struct to JSON)
func (u *Cranberry) MarshalJSON() ([]byte, error) {
    return json.Marshal(&CranberryJSON{
        CranberryAlias: (*CranberryAlias)(u),
        // Unexported or custom-formatted fields are listed here:
        Invisible:       u.invisible,
        CustomUnixEpoch: u.Custom.Unix(),
    })
}

// UnmarshalJSON unmarshals a Cranberry. (JSON to struct)
func (u *Cranberry) UnmarshalJSON(data []byte) error {
    temp := &CranberryJSON{
        CranberryAlias: (*CranberryAlias)(u),
    }
    if err := json.Unmarshal(data, &temp); err != nil {
        return err
    }

    // Copy the exported fields:
    *u = (Cranberry)(*(temp).CranberryAlias)
    // Each unexported field must be copied and/or converted individually:
    u.invisible = temp.Invisible
    u.Custom = time.Unix(temp.CustomUnixEpoch, 0) // Convert while copying.

    return nil
}

func main() {
    var out []byte
    var err error

    // Demonstration of marshalling: Marshal s (struct) to out ([]byte)
    fmt.Printf("Struct to JSON:\n")
    s := &Cranberry{Visible: 1, invisible: 2, Custom: time.Unix(1521492409, 0)}
    out, err = json.Marshal(s)
    if err != nil {
        panic(err)
    }
    fmt.Printf("      got=%v\n", string(out))
    fmt.Println(` expected={"visible":1,"invisible":2,"epoch":1521492409}`)

    // Demonstration of how to unmarshal: Unmarshal out ([]byte) to n (struct)
    fmt.Printf("JSON to struct:\n")
    var n = &Cranberry{}
    err = json.Unmarshal(out, n)
    if err != nil {
        panic(err)
    }
    fmt.Printf("      got=%+v\n", n)
    fmt.Println(` expected=&{Visible:1 invisible:2 Custom:2018-03-19 xx:46:49 xxxxx xxx}`)
}

输出如下所示:

$ go run minimal.go 
Struct to JSON:
      got={"visible":1,"invisible":2,"epoch":1521492409}
 expected={"visible":1,"invisible":2,"epoch":1521492409}
JSON to struct:
      got=&{Visible:1 invisible:2 Custom:2018-03-19 16:46:49 -0400 EDT}
 expected=&{Visible:1 invisible:2 Custom:2018-03-19 xx:46:49 xxxxx xxx}

我从 http://choly.ca/post/go-json-marshalling/ 那里得到了这个想法,他值得所有的赞扬。感谢@anderson-nascente 带领我发现博客 post.