【问题标题】:Call json.Unmarshal inside UnmarshalJSON function without causing stack overflow在 UnmarshalJSON 函数中调用 json.Unmarshal 而不会导致堆栈溢出
【发布时间】:2017-04-03 04:43:26
【问题描述】:

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

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

还有其他方法可以做到这一点吗?只需调用底层默认实现而不会导致这种情况?

【问题讨论】:

    标签: json serialization go


    【解决方案1】:

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

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

    这会产生一些运行时开销吗?编号引用自Spec: Conversions:

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

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

    【讨论】:

    • 不会将您的示例从 type person2 Person 重新构建为使用 type person2 *Person 以提供更清晰的信息吗?那么类型转换就是person2(p)而不是(*person2)(p)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-02-09
    • 2012-08-27
    • 1970-01-01
    • 2018-06-19
    • 2011-02-26
    相关资源
    最近更新 更多