我知道 Go JSON 问题非常多;现有的 stdlib JSON 包很笨重。关于 v2 的讨论正在进行中。在那之前,我希望有人可以在这里提供一些灵感,帮助您了解如何为可为空、可选/未定义和零值创建强大的解决方案。 Null、未定义和零值通常具有不同的语义(例如
JSON Merge Patch),因此区分它们很重要,但 Go 将它们全部归结为 Go 零值。使用指针,我们可以区分空/未定义和值,但无法区分空和未定义和值。 以下是我区分零、空和未定义的尝试:
尝试 1:
playgroundtype Optional[T any] struct {
Value *T
}
func (o *Optional[T]) MarshalJSON() ([]byte, error) { return json.Marshal(o.Value)}
func (o *Optional[T]) UnmarshalJSON(data []byte) error { o.Value = new(T); return json.Unmarshal(data, o.Value) }
type X struct {
Y Optional[*int] `json:"y,omitempty"` // Trying to make this optional AND nullable
}
这几乎有效:
✅ 解组
{}
X.Y.Value
为零✅ Unmarshal {"y": null}
X.Y.Value
为非零,*X.Y.Value
为零✅ Unmarshal {"y": 0}
X.Y.Value
为非零,*X.Y.Value
为非零,**X.Y.Value
为 0
❌ Marshal X.Y.Value
{}
,而是编组到 {"y":null}
stdlib encoding/json
omitempty
不适用于零值结构
X.Y.Value
*X.Y.Value
为零 -> {"y": null}
✅ Marshal X.Y.Value
*X.Y.Value
为非零,**X.Y.Value
为 0
-> {"y": 0}
type Optional[T any] struct {
Value T `json:",inline"`
}
func (o *Optional[T]) MarshalJSON() ([]byte, error) { return json.Marshal(&o.Value) }
func (o *Optional[T]) UnmarshalJSON(data []byte) error { return json.Unmarshal(data, &o.Value) }
type X struct {
Y *Optional[*int] `json:"y,omitempty"` // Trying to make this optional AND nullable
}
{}
X.Y
为零❌ Unmarshal {"y": null}
X.Y
为零
由于 X.Y
*Optional[*int]
),因此 null
值将 X.Y
设置为 nil,而不是调用 Optional[*int].UnmarshalJSON
。
{"y": 0}
X.Y
为非零,*X.Y.Value
为非零,*X.Y.Value
为 0
✅元帅X.Y
{}
✅ Marshal X.Y
X.Y.Value
为零 -> {"y": null}
✅ Marshal X.Y
X.Y.Value
为非零,*X.Y.Value
为 0
-> {"y": 0}
go vet
,不那么漂亮type Optional[T any] uintptr
func (o *Optional[T]) SetValue(t T) { *o = Optional[T](unsafe.Pointer(&t)) }
func (o *Optional[T]) UnsetValue() { *o = 0 }
func (o *Optional[T]) Value() *T { return (*T)(unsafe.Pointer(*o)) }
func (o *Optional[T]) MarshalJSON() ([]byte, error) { return json.Marshal((*T)(unsafe.Pointer(*o))) }
func (o *Optional[T]) UnmarshalJSON(data []byte) error {
t := new(T)
err := json.Unmarshal(data, t)
if err != nil {
return err
}
*o = Optional[T](unsafe.Pointer(t))
return nil
}
type X struct {
Y Optional[*int] `json:"y,omitempty"` // Trying to make this optional AND nullable
}
{}
X.Y.Value()
为零✅ Unmarshal {"y": null}
X.Y.Value()
为非零,*X.Y.Value()
为零✅ Unmarshal {"y": 0}
X.Y.Value()
为非零,*X.Y.Value()
为非零,**X.Y.Value()
为 0
✅元帅X.Y.UnsetValue()
{}
✅元帅X.Y.SetValue(nil)
{"y": null}
✅元帅X.Y.SetValue(<pointer to 0>)
{"y": 0}
未定义:
Optional[*Type].Value
*Optional[*Type].Value
**Optional[*Type].Value
null
显式解组为零值以外的值。我认为,与其乱搞指针,不如使用一个区分这三种情况的枚举来更简单; null、未定义和一个值。
例如这段代码:package main
import (
"bytes"
"encoding/json"
"fmt"
)
type FieldType int
const (
Undefined = FieldType(0)
Value = FieldType(1)
Null = FieldType(2)
)
type PatchField[T any] struct {
Type FieldType
V T
}
var (
nullToken = []byte("null")
)
func (pt *PatchField[T]) UnmarshalJSON(b []byte) error {
*pt = PatchField[T]{Type: Undefined}
if bytes.Equal(b, nullToken) {
pt.Type = Null
return nil
}
pt.Type = Value
return json.Unmarshal(b, &pt.V)
}
type X struct {
Y PatchField[int] `json:y`
}
func (pt PatchField[T]) String() string {
switch pt.Type {
case Undefined:
return "undefined"
case Null:
return "null"
default:
return fmt.Sprint(pt.V)
}
}
func main() {
cs := []string{`{"y": null}`, `{}`, `{"y": 42}`}
for _, c := range cs {
var x X
if err := json.Unmarshal([]byte(c), &x); err != nil {
fmt.Println(err)
} else {
fmt.Printf("%+v\n", x)
}
}
}