Custom MarshalJSON() never gets called in Go

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

Custom MarshalJSON() never gets called in Go

问题

我已经写了自定义版本的MarshalJSONUnmarshalJSON。我的UnmarshalJSON按照我想要的方式被调用,但是我无法让它与MarshalJSON一起工作。以下是总结我问题的代码:

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"os"
)

type myStruct struct {
	Data string `json:"data"`
}

func (s *myStruct) MarshalJSON() ([]byte, error) {
	return []byte(`{"data":"charlie"}`), nil
}

func (s *myStruct) UnmarshalJSON(b []byte) error {
	// 将字符串直接插入到Data成员中
	return json.Unmarshal(b, &s.Data)
}

func main() {
	// 创建一个初始内容为"alpha"的结构体
	ms := myStruct{"alpha"}

	// 使用自定义的UnmarshalJSON()将内容替换为"bravo"(成功)
	if err := json.NewDecoder(bytes.NewBufferString(`"bravo"`)).Decode(&ms); err != nil {
		log.Fatal(err)
	}

	// 使用自定义的MarshalJSON()获取"charlie"(不成功)
	if err := json.NewEncoder(os.Stdout).Encode(ms); err != nil {
		log.Fatal(err)
	}

	// 尝试另一种方法(不成功)
	if ret, err := json.Marshal(ms); err != nil {
		log.Fatal(err)
	} else {
		fmt.Println(string(ret))
	}

	// 验证Marshaler接口是否正确实现
	var marsh json.Marshaler
	marsh = &ms
	ret, _ := marsh.MarshalJSON()
	fmt.Println(string(ret)) // 输出"charlie"
}

简而言之,该程序以两种方式“自动”编码struct,然后最后手动调用MarshalJSON。我想要的结果是"charlie"。运行代码会生成以下输出:

{"data":"bravo"}
{"data":"bravo"}
{"data":"charlie"}

你可以在Go Playground上尝试运行:http://play.golang.org/p/SJ05S8rAYN

英文:

I've written custom versions of MarshalJSON and UnmarshalJSON. My UnmarshalJSON gets called the way I want it to, but I can't get it to work with MarshalJSON. Here's code that summarizes my problem:

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"os"
)

type myStruct struct {
	Data string `json:"data"`
}

func (s *myStruct) MarshalJSON() ([]byte, error) {
	return []byte(`{"data":"charlie"}`), nil
}

func (s *myStruct) UnmarshalJSON(b []byte) error {
	// Insert the string directly into the Data member
	return json.Unmarshal(b, &s.Data)
}

func main() {
	// Create a struct with initial content "alpha"
	ms := myStruct{"alpha"}

	// Replace content with "bravo" using custom UnmarshalJSON() (SUCCESSFUL)
	if err := json.NewDecoder(bytes.NewBufferString(`"bravo"`)).Decode(&ms); err != nil {
		log.Fatal(err)
	}

	// Use custom MarshalJSON() to get "charlie" back (UNSUCCESSFUL)
	if err := json.NewEncoder(os.Stdout).Encode(ms); err != nil {
		log.Fatal(err)
	}

	// Trying another method (UNSUCCESSFUL)
	if ret, err := json.Marshal(ms); err != nil {
		log.Fatal(err)
	} else {
		fmt.Println(string(ret))
	}

	// Verify that the Marshaler interface is correctly implemented
	var marsh json.Marshaler
	marsh = &ms
	ret, _ := marsh.MarshalJSON()
	fmt.Println(string(ret)) // Prints "charlie"
}

In short, the program encodes the struct "automatically" in two ways, and then finally calls MarshalJSON manually. The response I want is "charlie". Running the code generates the following output:

{"data":"bravo"}
{"data":"bravo"}
{"data":"charlie"}

Try it at Go Playground: http://play.golang.org/p/SJ05S8rAYN

答案1

得分: 49

在代码的这部分中,ms被复制到一个interface{}变量中:

// 尝试另一种方法(不成功)
if ret, err := json.Marshal(ms); err != nil {

问题在于,这个变量没有实现json.Marshaler接口,因为MarshalJSON不在myStruct方法集中(只在*myStruct中)。

修复的方法要么是(a)使你的MarshalJSON方法接受一个非指针接收器(这意味着它会得到结构体的副本:如果结构体很大可能会很耗费资源),要么是(b)将结构体的指针进行编组(正如Kavu在评论中提到的)。

这种行为的原因是,Go语言不允许你获取存储在接口变量中的值的指针,而是要求你在每次访问它时都要复制该值。虽然语言提供了语法糖将ms.MarshalJSON()转换为(&ms).MarshalJSON()以通过指针接收器访问方法,但对于存储在接口变量中的值来说,这是不可能的。因此,该方法不被认为是其方法集的一部分。

英文:

In this part of the code, ms gets copied into an interface{} variable:

// Trying another method (UNSUCCESSFUL)
if ret, err := json.Marshal(ms); err != nil {

The problem is that this variable does not implement the json.Marshaler interface, since MarshalJSON is not in the method set for myStruct (only for *myStruct).

The fix is to either (a) make your MarshalJSON method take a non-pointer receiver (which will mean it gets a copy of the struct: possibly costly if it is large), or (b) marshal a pointer to the struct (as Kavu mentioned in a comment).

The reason for this behaviour is that Go doesn't let you take a pointer to the value stored inside an interface variable, instead requiring you to make a copy of the value whenever you want to access it. While the language has syntactic sugar to convert ms.MarshalJSON() into (&ms).MarshalJSON() as a way to access the method with a pointer receiver, this can not be done for a value stored in an interface variable. For this reason, the method is not considered to be in its method set.

huangapple
  • 本文由 发表于 2014年1月28日 04:26:43
  • 转载请务必保留本文链接:https://go.coder-hub.com/21390979.html
匿名

发表评论

匿名网友

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

确定