英文:
Call json.Unmarshal inside UnmarshalJSON function without causing stack overflow
问题
我想在我的UnmarshalJSON
实现中执行一些额外的步骤来初始化数据结构。在该实现中调用json.Unmarshal(b, type)
会导致堆栈溢出。
JSON解码器不断尝试查找是否有自定义的UnmarshalJSON
实现,然后再次调用json.Unmarshal
。
有没有其他方法可以做到这一点?只调用底层的默认实现而不引起这个问题?
英文:
I wanted to perform some additional steps for initializing a data structure inside my implementation UnmarshalJSON
. Calling json.Unmarshal(b, type)
inside that implementation, naturally, causes a stack overflow.
The JSON decoder is continiously trying to look up, if there is a custom UnmarshalJSON
implementation which then again, calls json.Unmarshal
.
Is there another way to do this? Just call the underlying default implementation without causing this?
答案1
得分: 32
一种简单常见的避免这个问题或者保护自己免受其影响的方法是使用type
关键字创建一个新类型,并使用类型转换来传递该类型的值(该值可以是原始值,因为新类型具有原始类型作为其基础类型)。
这种方法有效的原因是type
关键字创建了一个新类型,而新类型将没有任何方法(它不会"继承"基础类型的方法)。
这会产生一些运行时开销吗?不会。引用自规范:转换:
> 针对数值类型之间的(非常量)转换或者与字符串类型之间的转换,有特定的规则。这些转换可能会改变x
的表示并产生运行时开销。所有其他转换只会改变类型而不会改变x
的表示。
让我们看一个例子。我们有一个具有数值类型Age
的Person
类型,并且我们希望确保Age
不能为负数(小于0
)。
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
func (p *Person) UnmarshalJSON(data []byte) error {
type person2 Person
if err := json.Unmarshal(data, (*person2)(p)); err != nil {
return err
}
// 反序列化后的后处理:
if p.Age < 0 {
p.Age = 0
}
return nil
}
进行测试:
var p *Person
fmt.Println(json.Unmarshal([]byte(`{"name":"Bob","age":10}`), &p))
fmt.Println(p)
fmt.Println(json.Unmarshal([]byte(`{"name":"Bob","age":-1}`), &p))
fmt.Println(p)
输出结果(在Go Playground上尝试):
<nil>
&{Bob 10}
<nil>
&{Bob 0}
当然,相同的技术也适用于自定义编组(MarshalJSON()
):
func (p *Person) MarshalJSON() ([]byte, error) {
// 编组前的预处理:
if p.Age < 0 {
p.Age = 0
}
type person2 Person
return json.Marshal((*person2)(p))
}
进行测试:
p = &Person{"Bob", 10}
fmt.Println(json.NewEncoder(os.Stdout).Encode(p))
p = &Person{"Bob", -1}
fmt.Println(json.NewEncoder(os.Stdout).Encode(p))
输出结果(在相同的Go Playground示例中):
{"name":"Bob","age":10}
<nil>
{"name":"Bob","age":0}
<nil>
当你为fmt
包定义String() string
方法以自定义文本表示时,并且你想要使用你修改后的默认字符串表示时,会出现非常类似的问题。在这里阅读更多相关信息:https://stackoverflow.com/questions/43065856/the-difference-between-t-and-t/43065979#43065979
英文:
An easy and common way to avoid this / protect from it is to create a new type with the type
keyword, and use a type conversion to pass a value of this type (the value may be your original value, type conversion is possible because the new type has the original type as its underlying type).
This works because the type
keyword creates a new type, and the new type will have zero methods (it does not "inherit" the methods of the underlying type).
Does this incur some run-time overhead? No. Quoting from Spec: Conversions:
> Specific rules apply to (non-constant) conversions between numeric types or to and from a string type. These conversions may change the representation of x
and incur a run-time cost. All other conversions only change the type but not the representation of x
.
Let's see an example. We have a Person
type with a numeric Age
, and we want to make sure the Age
cannot be negative (less than 0
).
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
func (p *Person) UnmarshalJSON(data []byte) error {
type person2 Person
if err := json.Unmarshal(data, (*person2)(p)); err != nil {
return err
}
// Post-processing after unmarshaling:
if p.Age < 0 {
p.Age = 0
}
return nil
}
Testing it:
var p *Person
fmt.Println(json.Unmarshal([]byte(`{"name":"Bob","age":10}`), &p))
fmt.Println(p)
fmt.Println(json.Unmarshal([]byte(`{"name":"Bob","age":-1}`), &p))
fmt.Println(p)
Output (try it on the Go Playground):
<nil>
&{Bob 10}
<nil>
&{Bob 0}
Of course the same technique works for custom marshaling (MarshalJSON()
) too:
func (p *Person) MarshalJSON() ([]byte, error) {
// Pre-processing before marshaling:
if p.Age < 0 {
p.Age = 0
}
type person2 Person
return json.Marshal((*person2)(p))
}
Testing it:
p = &Person{"Bob", 10}
fmt.Println(json.NewEncoder(os.Stdout).Encode(p))
p = &Person{"Bob", -1}
fmt.Println(json.NewEncoder(os.Stdout).Encode(p))
Output (on the same Go Playground example):
{"name":"Bob","age":10}
<nil>
{"name":"Bob","age":0}
<nil>
A very similar issue is when you define the String() string
method for custom text representation for the fmt
package, and you want to use the default string representation which you modify. Read more about it here: https://stackoverflow.com/questions/43065856/the-difference-between-t-and-t/43065979#43065979
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论