Go JSON 区分零值、空值和未定义值

问题描述 投票:0回答:1

我知道 Go JSON 问题非常多;现有的 stdlib JSON 包很笨重。关于 v2 的讨论正在进行中。在那之前,我希望有人可以在这里提供一些灵感,帮助您了解如何为可为空、可选/未定义和零值创建强大的解决方案。 Null、未定义和零值通常具有不同的语义(例如

JSON Merge Patch

),因此区分它们很重要,但 Go 将它们全部归结为 Go 零值。使用指针,我们可以区分空/未定义和值,但无法区分空和未定义和值。 以下是我区分零、空和未定义的尝试:

尝试 1:

playground

- 无法编组未定义的直观语法 type 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
      不适用于零值结构
      
      
    ✅ Marshal
  • X.Y.Value
  • 非零,
    *X.Y.Value
    为零 ->
    {"y": null}
    ✅ Marshal 
  • X.Y.Value
  • 为非零,
    *X.Y.Value
    为非零,
    **X.Y.Value
    0
    ->
    {"y": 0}
    
    
  • 尝试 2:
playground

- 无法解组 null,需要 *Optional[T] 才能真正可选 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
      
      
    ✅ Unmarshal
  • {"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}
    
    
  • 尝试 3:
playground

- 有效,不安全,失败 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}
    
    
  • 结束

我的偏好是尝试 1(显然,如果它有效的话)。语法对我来说似乎很直观:

未定义:
    Optional[*Type].Value
  • 为零
    null:
  • *Optional[*Type].Value
  • 为零
    值:
  • **Optional[*Type].Value
  • 是值
    
    
  • 有人有办法区分编组和解组中的零/空/未定义吗?

json go marshalling unmarshalling
1个回答
0
投票
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) } } }

	
© www.soinside.com 2019 - 2024. All rights reserved.