doujuyang1764 2017-04-03 04:43
浏览 159
已采纳

在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条回答 默认 最新

  • dtf76989 2017-04-03 06:54
    关注

    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: Type declarations:

    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: The difference between t and *t

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥30 关于#硬件工程#的问题:求一下解题思路
  • ¥15 运筹学对偶单纯行法构造扩充问题
  • ¥20 XP系统的老电脑一开机就提示找不到rundll.exe,付费求解
  • ¥15 milvus查询出来的score怎么转换成0-1之间的相似性
  • ¥15 多ip服务器站群如何搭建l2tp服务器
  • ¥15 lvgl V9移植到linux开发板
  • ¥15 VB.net中在窗体中创建一个button控件来关闭窗体,但是提示错误,我该怎么办
  • ¥15 网上下载好的程序但是arduinoIDE编程报错,运行不了,哪里出错了,能具体给改一下吗
  • ¥15 Sharepoint JS开发 付费技术指导
  • ¥15 输入程序运行仿真后,烟雾值不实时检测,变成固定值