在UnmarshalJSON函数内调用json.Unmarshal而不会导致堆栈溢出

时间:2017-04-03 04:43:26

标签: json serialization go

我想在我的实现UnmarshalJSON中初始化数据结构时执行一些额外的步骤。当然,在该实现中调用json.Unmarshal(b, type)会导致堆栈溢出。

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

还有其他办法吗?只是调用底层的默认实现而不引起这个?

1 个答案:

答案 0 :(得分:8)

避免这种情况或保护它的一种简单而常见的方法是使用type关键字创建新类型,并使用类型conversion传递此类型的值(值可能作为原始值,类型转换是可能的,因为新类型具有原始类型作为其基础类型。)

这是有效的,因为type关键字创建了一个新类型,而新类型将没有方法(它不会“继承”基础类型的方法)。

这会产生一些运行时开销吗?编号来自Spec: Type declarations:

  

特定规则适用于数字类型之间的(非常量)转换或字符串类型之间的转换。这些转化可能会更改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>

一个非常类似的问题是,为fmt包定义自定义文本表示的String() string方法,并且您希望使用您修改的默认字符串表示形式。在此处阅读更多相关信息:The difference between t and *t