Can I use MarshalJSON to add arbitrary fields to a json encoding in golang?

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

Can I use MarshalJSON to add arbitrary fields to a json encoding in golang?

问题

假设我已经写了以下代码片段。点击这里查看完整代码。

type Book struct {
  Title  string
  Author string
}

func main() {
  ms := Book{"Catch-22", "Joseph Heller"}
  out, err := json.MarshalIndent(ms, "", "  ")
  if err != nil {
    log.Fatalln(err)
  }
  fmt.Println(string(out))
}

这段代码的输出结果如下,正如我所期望的那样:

{
  "Title": "Catch-22",
  "Author": "Joseph Heller"
}

假设我想在JSON输出中添加一个字段,但不在Book结构体中包含它,比如一个genre(类型):

{
  "Title": "Catch-22",
  "Author": "Joseph Heller",
  "Genre": "Satire"
}

我可以使用MarshalJSON()Marshal()过程中添加一个任意的字段吗?类似这样:

func (b *Book) MarshalJSON() ([]byte, error) {
    // 一些代码
}

其他的答案让我觉得这是可能的,但我还在努力弄清楚具体的实现方法。

英文:

Suppose I've written the following code snippet. Full code on the playground here for those inclined.

type Book struct {
  Title        string
  Author       string
}

func main() {
  ms := Book{"Catch-22", "Joseph Heller"}
  out, err := json.MarshalIndent(ms, "", "  ")
  if err != nil {
    log.Fatalln(err)
  }
  fmt.Println(string(out))
}

This code outputs the following, exactly as I'd expect:

{
  "Title": "Catch-22",
  "Author": "Joseph Heller"
}

Suppose for a moment I wanted to add a field to the JSON output without including it in the Book struct. Perhaps a genre:

{
  "Title": "Catch-22",
  "Author": "Joseph Heller",
  "Genre": "Satire"
}

Can I use MarshalJSON() to add an arbitrary field to the JSON payload on Marshal()? Something like:

func (b *Book) MarshalJSON() ([]byte, error) {
    // some code
}

Other answers make me think this should be possible, but I'm struggling to figure out the implementation.

答案1

得分: 42

以下是翻译好的内容:

这是比我之前的回答更好的答案。

type FakeBook Book

func (b Book) MarshalJSON() ([]byte, error) {
    return json.Marshal(struct {
        FakeBook
        Genre string
    }{
        FakeBook: FakeBook(b),
        Genre:    "Satire",
    })
}

由于匿名结构字段是“合并”的(还有一些其他考虑因素),我们可以利用这一点避免重新映射各个字段。请注意使用FakeBook类型以避免否则会发生的无限递归。

Playground: http://play.golang.org/p/21YXhB6OyC

英文:

Here's a better answer than my previous one.

type FakeBook Book

func (b Book) MarshalJSON() ([]byte, error) {
	return json.Marshal(struct {
		FakeBook
		Genre string
	}{
		FakeBook: FakeBook(b),
		Genre:    "Satire",
	})
}

Since anonymous struct fields are "merged" (with a few additional considerations) we can use that to avoid remapping the individual fields. Note the use of the FakeBook type to avoid the infinite recursion which would otherwise occur.

Playground: http://play.golang.org/p/21YXhB6OyC

答案2

得分: 4

这个问题的一个可能答案是使用结构体字面量(代码在这里),尽管我希望有一个更通用的解决方案,不需要重新映射结构体的所有字段:

func (b *Book) MarshalJSON() ([]byte, error) {
    return json.Marshal(struct{
        Title    string
        Author   string
        Genre    string
    } {
        Title: b.Title,
        Author: b.Author,
        Genre: "Satire",
    })
}
英文:

One possible answer to this question is a struct literal (code here), although I'm hoping for something a bit more general, which doesn't require remapping all of the struct's fields:

func (b *Book) MarshalJSON() ([]byte, error) {
	return json.Marshal(struct{
		Title    string
		Author   string
		Genre    string
	} {
		Title: b.Title,
		Author: b.Author,
		Genre: "Satire",
	})
}

答案3

得分: 4

编组(Marshalling)一个map是解决这个问题的另一种方法。

tmap := make(map[string]interface{})

tmap["struct"] = struct {
    StructValue string `json:"struct_value"`
}{
    "Value 02",
}

tmap["string"] = "Value 01"

out, err := json.MarshalIndent(tmap, "", "  ")
if err != nil {
    log.Fatalln(err)
}
fmt.Println(string(out))

这将输出:

{
  "string": "Value 01",
  "struct": {
    "struct_value": "Value 02"
  }
}

当你有许多任意的键名时,这可能是一个很好的解决方案。

Playground: https://play.golang.org/p/Umy9rtx2Ms

英文:

Marshalling a map is another way around the problem.

<!-- language: lang-golang -->

tmap := make(map[string]interface{})

tmap[&quot;struct&quot;] = struct {
	StructValue string `json:&quot;struct_value&quot;`
}{
	&quot;Value 02&quot;,
}

tmap[&quot;string&quot;] = &quot;Value 01&quot;

out, err := json.MarshalIndent(tmap, &quot;&quot;, &quot;  &quot;)
if err != nil {
	log.Fatalln(err)
}
fmt.Println(string(out))

This will output:

<!-- language: lang-json -->

{
  &quot;string&quot;: &quot;Value 01&quot;,
  &quot;struct&quot;: {
    &quot;struct_value&quot;: &quot;Value 02&quot;
  }
}

Where you have many arbitrary key names this could be a good solution.

Playground: https://play.golang.org/p/Umy9rtx2Ms

答案4

得分: 3

这是一种处理方法:

type Object struct {
	A string
	B int
	Extra map[string]interface{} `json:"-"`
}

func (o Object) MarshalJSON() ([]byte, error) {
	type Object_ Object
	b, err := json.Marshal(Object_(o))
	if err != nil {
		return nil, err
	}
	if o.Extra == nil || len(o.Extra) == 0 {
		return b, nil
	}
	m, err := json.Marshal(o.Extra)
	if err != nil {
		return nil, err
	}
	if len(b) == 2 {
		return m, nil
	} else {
		b[len(b)-1] = ','
		return append(b, m[1:]...), nil
	}
}

你可以向 Extra map 添加任何额外的字段,它们将以不嵌套的方式出现在输出中。

Go Playground

英文:

This is one method of handling it:

type Object struct {
	A string
	B int
	Extra map[string]interface{} `json:&quot;-&quot;`
}

func (o Object) MarshalJSON() ([]byte, error) {
	type Object_ Object
	b, err := json.Marshal(Object_(o))
	if err != nil {
		return nil, err
	}
	if o.Extra == nil || len(o.Extra) == 0 {
		return b, nil
	}
	m, err := json.Marshal(o.Extra)
	if err != nil {
		return nil, err
	}
	if len(b) == 2 {
		return m, nil
	} else {
		b[len(b)-1] = &#39;,&#39;
		return append(b, m[1:]...), nil
	}
}

You can add whatever additional fields to the Extra map and they will appear without nesting in the output.

Go Playground

huangapple
  • 本文由 发表于 2014年4月14日 01:25:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/23045884.html
匿名

发表评论

匿名网友

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

确定