You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Golang:指针为nil/null时自定义UnmarshalJSON/MarshalJSON失效问题

Go指针类型JSON编解码:null/nil时自定义方法不触发的问题及解决

我尝试在指针类型上实现自定义的UnmarshalJSON和MarshalJSON方法,但当JSON数据为null或指针值为nil时,这些方法均不会被触发,示例代码及输出如下:

package main

import (
    "encoding/json"
    "fmt"
)

type A struct {
    B *B `json:"b,omitempty"`
}

type B int

// Only for displaying value instead of
// pointer address when calling `fmt.Println`
func (b *B) String() string {
    if b == nil {
        return "nil"
    }

    return fmt.Sprintf("%d", *b)
}

// This function is not triggered when json
// data contains null instead of number value
func (b *B) UnmarshalJSON(data []byte) error {
    fmt.Println("UnmarshalJSON on B called")

    var value int
    if err := json.Unmarshal(data, &value); err != nil {
        return err
    }

    if value == 7 {
        *b = B(3)
    }

    return nil
}

// This function is not triggered when `B`
// is pointer type and has `nil` value
func (b *B) MarshalJSON() ([]byte, error) {
    fmt.Println("MarshalJSON on B called")

    if b == nil {
        return json.Marshal(0)
    }

    if *b == 3 {
        return json.Marshal(7)
    }

    return json.Marshal(*b)
}

func main() {
    var a A

    // this won't call `UnmarshalJSON`
    json.Unmarshal([]byte(`{ "b": null }`), &a)
    fmt.Printf("a: %+v\n", a)

    // this won't call `MarshalJSON`
    b, _ := json.Marshal(a)
    fmt.Printf("b: %s\n\n", string(b))

    // this would call `UnmarshalJSON`
    json.Unmarshal([]byte(`{ "b": 7 }`), &a)
    fmt.Printf("a: %+v\n", a)

    // this would call `MarshalJSON`
    b, _ = json.Marshal(a)
    fmt.Printf("b: %s\n\n", string(b))
}

输出:

a: {B:nil}
b: {}

UnmarshalJSON on B called
a: {B:3}
MarshalJSON on B called
b: {"b":7}

我的问题如下:

  1. 为何指针类型对应null/nil值时,UnmarshalJSON/MarshalJSON不会被调用?
  2. 如何在指针为null/nil时也能触发这些方法,而非在A类型上实现编解码方法并修改b属性?

问题1解答

Go标准库encoding/json对指针类型的编解码有特殊处理逻辑:

  • Unmarshal阶段:当JSON值为null时,库会直接将目标指针设为nil,不会调用指针类型的UnmarshalJSON方法——因为此时指针本身是nil,没有可调用方法的实例接收者。
  • Marshal阶段:当指针值为nil时,库会直接将其序列化为null(带omitempty标签则忽略字段),同样不会触发MarshalJSON——nil指针无法作为方法接收者,自然无法调用自定义方法。

简单来说,自定义编解码方法需要一个非nil的接收者实例才能执行,而null/nil场景下接收者不存在,所以方法不会被触发。

问题2解答

要覆盖null/nil场景的编解码逻辑,不能直接在*B类型上实现方法,需要定义一个包装类型,将*B作为包装类型的内部字段,然后在包装类型上实现UnmarshalJSON和MarshalJSON。这样无论JSON是null还是有效值,都会调用包装类型的方法,我们可以在方法内部处理nil情况。

修改后的示例代码:

package main

import (
    "encoding/json"
    "fmt"
)

type A struct {
    B BWrapper `json:"b,omitempty"`
}

type B int

// 定义包装类型,内部持有*B指针
type BWrapper struct {
    ptr *B
}

// 实现String方法,用于打印展示
func (bw BWrapper) String() string {
    if bw.ptr == nil {
        return "nil"
    }
    return fmt.Sprintf("%d", *bw.ptr)
}

// 在包装类型上实现UnmarshalJSON,覆盖null场景
func (bw *BWrapper) UnmarshalJSON(data []byte) error {
    fmt.Println("UnmarshalJSON on BWrapper called")
    // 处理JSON null的情况
    if string(data) == "null" {
        // 自定义逻辑:比如给指针设置默认值0
        val := B(0)
        bw.ptr = &val
        return nil
    }
    // 处理正常数值的情况
    var value int
    if err := json.Unmarshal(data, &value); err != nil {
        return err
    }
    if value == 7 {
        val := B(3)
        bw.ptr = &val
    } else {
        val := B(value)
        bw.ptr = &val
    }
    return nil
}

// 在包装类型上实现MarshalJSON,覆盖指针nil场景
func (bw BWrapper) MarshalJSON() ([]byte, error) {
    fmt.Println("MarshalJSON on BWrapper called")
    if bw.ptr == nil {
        // 自定义nil时的序列化值,比如返回0
        return json.Marshal(0)
    }
    if *bw.ptr == 3 {
        return json.Marshal(7)
    }
    return json.Marshal(*bw.ptr)
}

func main() {
    var a A

    // 现在会触发UnmarshalJSON
    json.Unmarshal([]byte(`{ "b": null }`), &a)
    fmt.Printf("a: %+v\n", a)

    // 现在会触发MarshalJSON
    b, _ := json.Marshal(a)
    fmt.Printf("b: %s\n\n", string(b))

    // 正常数值场景依然触发
    json.Unmarshal([]byte(`{ "b": 7 }`), &a)
    fmt.Printf("a: %+v\n", a)

    b, _ = json.Marshal(a)
    fmt.Printf("b: %s\n\n", string(b))
}

输出结果:

UnmarshalJSON on BWrapper called
a: {B:0}
MarshalJSON on BWrapper called
b: {"b":0}

UnmarshalJSON on BWrapper called
a: {B:3}
MarshalJSON on BWrapper called
b: {"b":7}

关键说明:

  • 包装类型BWrapper持有*B作为内部字段,它本身是值类型(也可以定义为指针类型,按需调整),其编解码方法可以覆盖所有场景。
  • 在UnmarshalJSON中先判断JSON是否为null,自定义处理逻辑;在MarshalJSON中判断内部指针是否为nil,返回自定义序列化结果。
  • 这种方式不需要修改A类型的编解码逻辑,所有处理都封装在B相关的包装类型中,符合单一职责原则。

内容的提问来源于stack exchange,提问作者mikolaj semeniuk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 18:25:30