Golang Marshal / Unmarshal JSON包含导出和未导出的字段

时间:2015-01-20 00:15:20

标签: json go marshalling unmarshalling

我已经看到很多方法来编组/解组只有未导出字段的结构。但是我怎样才能在混合领域做到这一点?

给出一个结构:

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)
}

有办法做到这一点吗?或者我应该重新思考我的数据结构,也许只是导出该字段?

注意:我知道我可以手动执行每个字段,但是如果可能的话,我希望避免更新代码更易于管理。

2 个答案:

答案 0 :(得分:6)

您可以创建一个特定的结构来处理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
}

答案 1 :(得分:0)

另一种解决方案完全正常,但需要更新 在许多地方任何时候一个领域都会改变。这个解决方案更容易 维持。

使用类型别名和 结构嵌入,我们可以创建一个更干的解决方案。新 字段自动按预期工作,除非它们未导出 或要求自定义格式。在这种情况下,最少量的工作 是必需的:只需在*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来引导我发现博客文章。