Golang使用Marshal/Unmarshal JSON处理既有导出字段又有未导出字段的情况。

huangapple go评论101阅读模式
英文:

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

问题

我看到了很多种方法可以对只有未导出字段的结构进行编组/解组。但是如何处理混合字段呢?

给定一个结构体:

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

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

**注意:**我知道可以手动处理每个字段,但如果可能的话,我想避免这样做,以便更容易更新代码。

英文:

I have seen plenty of ways to marshal/unmarshal structs that only have unexported fields. But how can I do this with mixed fields?

Given a struct:

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

How can I write MarshalJSON/UnmarshalJSON functions so that fieldA is transported along with FieldB and FieldC?

The following compiles, but then overflows the call stack when I run it. My guess is I am recursively marshalling the object, but I am not sure of how else to preserve both exported and unexported fields when encoding.

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

Is there a way to do this? Or should I re-think my data structures and maybe just export the field anyway?

Note: I'm aware I can do each field manually, but I'd like to avoid that if possible to make updating the code more manageable.

答案1

得分: 14

你可以创建一个特定的结构来处理JSON序列化消息。以下是示例代码:

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{
        FieldA: t.fieldA,
        FieldB: t.FieldB,
        FieldC: 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
}

这段代码定义了两个结构体:TestTestJSONTest结构体用于存储数据,而TestJSON结构体用于JSON序列化和反序列化。MarshalJSON方法将Test结构体转换为TestJSON结构体,并使用json.Marshal函数进行序列化。UnmarshalJSON方法将JSON数据反序列化为TestJSON结构体,并将其值赋给Test结构体的字段。

你可以根据需要修改TestTestJSON结构体的字段和类型,以适应你的实际需求。

英文:

You can create a specific structure to handle the JSON serialization message: 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
}

答案2

得分: 6

另一种解决方案完全可行,但每次字段更改时都需要在许多地方进行更新。这种解决方案更易于维护。

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

以下是翻译好的代码:

package main

/*
Go JSON模块无法访问结构体中的未导出字段。那么如何处理这些字段呢?

这个示例演示了http://choly.ca/post/go-json-marshalling/中的解决方案,其中我们有一个第二个结构体嵌入了主要结构体,但添加了用于公开未导出字段的字段。然后,我们编写MarshalJSON()和UnmarshalJSON()函数来完成正确的操作。

这也有助于在需要自定义格式的字段的情况下使用。
*/

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

// Cranberry存储数据。
// Visible:这个字段是导出的,JSON会像通常一样显示它。
// invisible:这个字段未导出,但我们希望它包含在JSON中。
// Custom:这个字段具有自定义的输出格式。我们将其存储为time.Time,但在JSON中出现时,应该以Unix Epoch格式显示。
type Cranberry struct {
	Visible   int       `json:"visible"`
	invisible int       // 这里没有标签
	Custom    time.Time `json:"-"`
}

// CranberryAlias是Cranberry的别名。我们使用别名是因为别名不包含任何函数,我们需要一个没有定义MarshalJSON/UnmarshalJSON的结构体,否则会得到一个递归定义。
type CranberryAlias Cranberry

// CranberryJSON表示我们如何将Cranberry表示给JSON包。
type CranberryJSON struct {
	*CranberryAlias       // 所有导出的字段。
	Invisible       int   `json:"invisible"`
	CustomUnixEpoch int64 `json:"epoch"`
	// FYI:json标签"invisble"和"epoch"可以是任何有效的JSON标签。
	// 这完全取决于我们希望如何在外部呈现JSON。
}

// MarshalJSON将Cranberry编组为JSON。
func (u *Cranberry) MarshalJSON() ([]byte, error) {
	return json.Marshal(&CranberryJSON{
		CranberryAlias: (*CranberryAlias)(u),
		// 在这里列出未导出或自定义格式的字段:
		Invisible:       u.invisible,
		CustomUnixEpoch: u.Custom.Unix(),
	})
}

// UnmarshalJSON将JSON解组为Cranberry。
func (u *Cranberry) UnmarshalJSON(data []byte) error {
	temp := &CranberryJSON{
		CranberryAlias: (*CranberryAlias)(u),
	}
	if err := json.Unmarshal(data, &temp); err != nil {
		return err
	}

	// 复制导出的字段:
	*u = (Cranberry)(*(temp).CranberryAlias)
	// 每个未导出的字段必须单独复制和/或转换:
	u.invisible = temp.Invisible
	u.Custom = time.Unix(temp.CustomUnixEpoch, 0) // 复制时进行转换。

	return nil
}

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

	// 演示编组:将s(结构体)编组为out([]byte)
	fmt.Printf("结构体到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("      得到=%v\n", string(out))
	fmt.Println(`  期望={ "visible": 1, "invisible": 2, "epoch": 1521492409 }`)

	// 演示解组:将out([]byte)解组为n(结构体)
	fmt.Printf("JSON到结构体:\n")
	var n = &Cranberry{}
	err = json.Unmarshal(out, n)
	if err != nil {
		panic(err)
	}
	fmt.Printf("      得到=%+v\n", n)
	fmt.Println(`  期望=&{Visible:1 invisible:2 Custom:2018-03-19 16:46:49 -0400 EDT}`)
}

输出如下:

$ go run minimal.go 
结构体到JSON:
得到={ "visible": 1, "invisible": 2, "epoch": 1521492409 }
期望={ "visible": 1, "invisible": 2, "epoch": 1521492409 }
JSON到结构体:
得到=&{Visible:1 invisible:2 Custom:2018-03-19 16:46:49 -0400 EDT}
期望=&{Visible:1 invisible:2 Custom:2018-03-19 xx:46:49 xxxxx xxx}

我从http://choly.ca/post/go-json-marshalling/得到了这个想法,他应该得到所有的赞誉。还要感谢@anderson-nascente,他带领我发现了这篇博文。

英文:

The other solution is completely functional but it requires updates
in many places any time a field changes. This solution is easier
to maintain.

By using a type alias and
struct embedding, we can create a solution that is more DRY. New
fields automatically work as expected, unless they are unexported
or require custom formatting. In that case, a minimal amount of work
is required: simply list the special field in the *JSON struct and
include conversion expressions in MarshalJSON and UnmarshalJSON.

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

The output looks like this:

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

I got this idea from http://choly.ca/post/go-json-marshalling/ who deserves all the credit. Credit also goes to @anderson-nascente who lead me to discover the blog post.

huangapple
  • 本文由 发表于 2015年1月20日 08:15:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/28035784.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定