一尘不染

在UnmarshalJSON函数中调用json.Unmarshal而不引起堆栈溢出

go

我想执行一些其他步骤来初始化实现中的数据结构UnmarshalJSONjson.Unmarshal(b, type)在该实现内调用自然会导致堆栈溢出。

JSON解码器会不断尝试查找,如果有自定义UnmarshalJSON实现,然后再次调用json.Unmarshal

还有另一种方法吗?只是调用基本的默认实现而不会导致此问题?


阅读 337

收藏
2020-07-02

共1个答案

一尘不染

避免这种情况/避免这种情况发生的一种简单且常见的方法是type使用关键字创建一个新类型,并使用类型转换来传递该类型的值(该值可能是您的原始值,因为新的类型具有原始类型作为其基础类型)。

之所以type可行,是因为关键字创建了一个新类型,并且该新类型将具有零个方法(它不会“继承”基础类型的方法)。

这会产生一些运行时开销吗?编号。从规格报价:转换:

特定规则适用于数字类型之间或字符串类型之间的(非恒定)转换。这些转换可能会更改的表示形式x并产生运行时成本。
所有其他转换只会更改类型,而不会更改的表示形式x

让我们来看一个例子。我们有一个Person带有数字的类型Age,我们要确保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
    }

    // Post-processing after unmarshaling:
    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) {
    // Pre-processing before marshaling:
    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>

一个非常相似的问题是,当您String()stringfmt包定义自定义文本表示方法时,您想使用所修改的默认字符串表示形式。

2020-07-02