Go

Go开发的各种坑 - struct序列化与反序列化

2023-05-09  本文已影响0人  红薯爱帅

1. 概述

Golang中,巨大的坑就是struct的序列化和反序列化。

struct的字段初始值,是Go零值,例如0、""、false。在CRUD操作中,需要两次序列化和反序列化,json<-->struct<-->db,存在的问题:

基于此,简单方法,只支持全量更新,且不区分零值和nil,会带来诸多不便。

如果要区分,解决办法有几个:

本文将对上述两种方法举例说明。

当然,也有其他怪招,比如

2. 通过指针的方式

package main

import (
    "encoding/json"
    "log"
)

type Foo struct{ Val *int }

func do(bytes []byte) (Foo, error) {
    var a Foo
    err := json.Unmarshal(bytes, &a)
    return a, err
}

func testDeserialize() {
    notSet := []byte(`{}`)
    setNull := []byte(`{"val": null}`)
    setValid := []byte(`{"val": 123}`)
    setWrongType := []byte(`{"val": "123"}`)

    a, err := do(notSet)
    log.Printf("NotSet      |value:%v |err: %v\n", a.Val, err)
    a, err = do(setNull)
    log.Printf("SetNull     |value:%v |err: %v\n", a.Val, err)
    a, err = do(setValid)
    log.Printf("SetValid    |value:%d   |err: %v\n", *a.Val, err) // 注意:实际应用时,需要先判断a.Val是否为nil
    a, err = do(setWrongType)
    log.Printf("SetWrongType|value:%d     |err: %v\n", *a.Val, err) // 注意,这时候a.Val居然是0,而不是nil
}

func testSerialize() {
    notSet := Foo{}
    setNull := Foo{nil}
    setValid := Foo{&[]int{22}[0]}

    res, err := json.Marshal(notSet)
    log.Printf("NotSet |result:%s |err: %v\n", res, err)
    res, err = json.Marshal(setNull)
    log.Printf("setNull |result:%s |err: %v\n", res, err)
    res, err = json.Marshal(setValid)
    log.Printf("setValid |result:%s |err: %v\n", res, err)
}

func main() {
    log.Println("deserialize test ...")
    testDeserialize()
    log.Println("serialize test ...")
    testSerialize()
}
% go run test_null.go
2023/05/10 15:10:12 deserialize test ...
2023/05/10 15:10:12 NotSet      |value:<nil> |err: <nil>
2023/05/10 15:10:12 SetNull     |value:<nil> |err: <nil>
2023/05/10 15:10:12 SetValid    |value:123   |err: <nil>
2023/05/10 15:10:12 SetWrongType|value:0     |err: json: cannot unmarshal string into Go struct field Foo.Val of type int
2023/05/10 15:10:12 serialize test ...
2023/05/10 15:10:12 NotSet |result:{"Val":null} |err: <nil>
2023/05/10 15:10:12 setNull |result:{"Val":null} |err: <nil>
2023/05/10 15:10:12 setValid |result:{"Val":22} |err: <nil>

3. 自定义类型

package main

import (
    "encoding/json"
    "log"
    "strconv"
)

type Int struct {
    Exists bool // 表示是否存在
    IsNull bool // 表示是否为null
    Value  int
}

// UnmarshalJSON 自定义反序列化方法
func (i *Int) UnmarshalJSON(data []byte) error {
    // 如果调用了该方法,说明设置了该值
    i.Exists = true
    if string(data) == "null" {
        // 表明该字段的值为 null
        i.IsNull = true
        return nil
    } else {
        i.IsNull = false
    }

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

// MarshalJSON 自定义序列化方法
func (i Int) MarshalJSON() ([]byte, error) {
    if !i.Exists || i.IsNull {
        return []byte("null"), nil // 注意:必须是小写null,不能是NULL、Null,原因Json不允许
    }
    return []byte(strconv.Itoa(i.Value)), nil
}

type Some struct{ Val Int }

func do(bytes []byte) (Some, error) {
    var a struct{ Val Int }
    err := json.Unmarshal(bytes, &a)
    return a, err
}

func testDeserialize() {
    notSet := []byte(`{}`)
    setNull := []byte(`{"val": null}`)
    setValid := []byte(`{"val": 123}`)
    setWrongType := []byte(`{"val": "123"}`)

    a, err := do(notSet)
    log.Printf("NotSet      |Exists:%t |IsNull:%t |Value: %d |err: %v\n", a.Val.Exists, a.Val.IsNull, a.Val.Value, err)
    a, err = do(setNull)
    log.Printf("SetNull     |Exists:%t  |IsNull:%t  |Value: %d |err: %v\n", a.Val.Exists, a.Val.IsNull, a.Val.Value, err)
    a, err = do(setValid)
    log.Printf("SetValid    |Exists:%t  |IsNull:%t |Value: %d |err: %v\n", a.Val.Exists, a.Val.IsNull, a.Val.Value, err)
    a, err = do(setWrongType)
    log.Printf("SetWrongType|Exists:%t  |IsNull:%t |Value: %d |err: %v\n", a.Val.Exists, a.Val.IsNull, a.Val.Value, err)
}

func testSerialize() {
    notSet1 := Some{Int{Exists: false}}
    notSet2 := Some{Int{Exists: false}}
    setNull := Some{Int{Exists: true, IsNull: true}}
    setValid := Some{Val: Int{Exists: true, IsNull: false, Value: 33}}

    res, err := json.Marshal(notSet1)
    log.Printf("NotSet1 |result:%s |err: %v\n", res, err)
    res, err = json.Marshal(notSet2)
    log.Printf("notSet2 |result:%s |err: %v\n", res, err)
    res, err = json.Marshal(setNull)
    log.Printf("setNull |result:%s |err: %v\n", res, err)
    res, err = json.Marshal(setValid)
    log.Printf("setValid |result:%s |err: %v\n", res, err)
}

func main() {
    log.Println("deserialize test ...")
    testDeserialize()
    log.Println("serialize test ...")
    testSerialize()
}
% go run test_json.go
2023/05/10 15:15:50 deserialize test ...
2023/05/10 15:15:50 NotSet      |Exists:false |IsNull:false |Value: 0 |err: <nil>
2023/05/10 15:15:50 SetNull     |Exists:true  |IsNull:true  |Value: 0 |err: <nil>
2023/05/10 15:15:50 SetValid    |Exists:true  |IsNull:false |Value: 123 |err: <nil>
2023/05/10 15:15:50 SetWrongType|Exists:true  |IsNull:false |Value: 0 |err: json: cannot unmarshal string into Go struct field .Val of type int
2023/05/10 15:15:50 serialize test ...
2023/05/10 15:15:50 NotSet1 |result:{"Val":null} |err: <nil>
2023/05/10 15:15:50 notSet2 |result:{"Val":null} |err: <nil>
2023/05/10 15:15:50 setNull |result:{"Val":null} |err: <nil>
2023/05/10 15:15:50 setValid |result:{"Val":33} |err: <nil>

4. 总结

在实际应用中,基本不用区分json的null未设置,所以,都可以一并对应到struct的nil,认为用户未设置

上述两种办法都是对Golang基本数据类型的补充,在使用过程中,会存在诸多不变,例如

所以,在Golang官方没有改变的情况下,还是尽量使用基本数据类型,否则,操作起来比较麻烦。

另外,在对空数组、空字典的序列化,是符合预期的,能区分开nil和[]、{}。

json.Marshal() will return null for var myslice []int and [] for initialized slice myslice := []int{}

5. Refer

上一篇 下一篇

猜你喜欢

热点阅读