掘金 后端 ( ) • 2024-05-16 09:55

1. 写在最前面

笔者最近在实现将内部通知系统的数据定义转化为产品定义的对外提供的数据结构。

  • 举例说明内部系统数据定义返回的结构定义有如下几种:

    • {"magName": "uploader", "status": 0}
    • {"magName": "uploaded", "fileList": ["testa", "testb"]}
    • {"magName": "upload_start", "fileList": "test"}
    • {"magName": "uploading", "progress": 1000}

在将如上数据结构转换的时候有两种思路

  • 内部通知系统的数据结构 1:1 跟产品外部定义结构进行映射
  • 内部通知系统的数据结构 n:1 跟产品外部定义的结构进行映射

在进一步拆解问题,在做 n:1 映射的时候,需要解决的是如何定义一个通用的产品定义结构,能够按需根据产品定义进行进行映射。该结构需要解决如下两件事情:

  • fileList 字段:可以设置两种不同类型,并且支持对改不同类型的返回进行补充
  • status 字段:可以区分出空字段还是未设置字段

2. 字段区分出空字段还是未设置字段

2.1 问题描述

在配置了 omitempty tag 的字段设置的值为默认值时,对该字段做 marshal 的时候,该字段会被忽略。

例子:

package main

import (
    "encoding/json"
    "fmt"
)

type Message struct {
    Status  int    `json:"status,omitempty"`
    MsgName string `json:"msgName,omitempty"`
}

func main() {
    //1. status 为默认值 0 时,做 unmarshal 可以解析出该字段
    test := `{"status": 0, "msgName": "hello"}`
    var m Message
    err := json.Unmarshal([]byte(test), &m)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Printf("%v\n", m)
    //2. status 为默认值 0 时,做 marshal 时不会输出该字段
    data, err := json.Marshal(m)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Printf("%s\n", data)
}

输出:

$> go run main.go
unmarshal: {0 hello}
marshal: {"msgName":"hello"}

2.2 解决

将可能为默认值的字段设置为指针类型,比如 int 设置为 *int

package main

import (
    "encoding/json"
    "fmt"
)

type Message struct {
    Status  *int   `json:"status,omitempty"`
    MsgName string `json:"msgName,omitempty"`
}

func main() {
    //1. status 为默认值 0 时,做 unmarshal 可以解析出该字段
    test := `{"status": 0, "msgName": "hello"}`
    var m Message
    err := json.Unmarshal([]byte(test), &m)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Printf("unmarshal: %v\n", m)
    //2. status 为默认值 0 时,做 marshal 会输出该字段
    data, err := json.Marshal(m)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Printf("marshal: %s\n", data)
}

输出:

$> go run main.go
unmarshal: {0x14000110108 hello}
marshal: {"status":0,"msgName":"hello"}

注:设置了指针类型的字段,如果原始的字段不存在时,则结构体字段为空(nil)

3. 字段支持多种类型 & 按需做不同类型处理

3.1 问题描述

某个指定字段支持配置两种类型,同时需要不同类型做产品指定的加工处理。

package main

import (
    "encoding/json"
    "fmt"
)

type Message struct {
    Status   *int   `json:"status,omitempty"`
    MsgName  string `json:"msgName,omitempty"`
    FileList any    `json:"fileList,omitempty"`
}

func fixFileList(fileList any) any {
    switch fileList.(type) {
    case string:
        fileList = fmt.Sprintf("string type [%s]", fileList.(string))
        return fileList
        fmt.Printf("fileList: %v\n", fileList)
    case []interface{}:
        fileListArr := fileList.([]interface{})
        for i, _ := range fileListArr {
            fileListArr[i] = fmt.Sprintf("array type [%s]", fileListArr[i])
        }
        return fileListArr
    }
    return "unknown"
}

func main() {
    //1. FileList 字段为 string 类型
    test := `{"status": 0, "msgName": "hello", "fileList": "world"}`
    var m Message
    err := json.Unmarshal([]byte(test), &m)
    if err != nil {
        fmt.Println(err)
    }
    m.FileList = fixFileList(m.FileList)
    fmt.Printf("unmarshal 1: %v\n", m)

    //2. FileList 字段为 array 类型
    test = `{"status": 0, "msgName": "hello", "fileList": ["world", "world2"]}`
    err = json.Unmarshal([]byte(test), &m)
    if err != nil {
        fmt.Println(err)
    }
    m.FileList = fixFileList(m.FileList)
    fmt.Printf("unmarshal 2: %v\n", m)

}

输出:

$> go run main.go
unmarshal 1: {0x1400000e220 hello string type [world]}
unmarshal 2: {0x1400000e220 hello [array type [world] array type [world2]]}

3.2 解决

解决方案分为两个步骤:

  • 将该字段设置为 any 或者 insterface 类型
  • 然后在根据断言的类型不同做不同的产品展示结果补充

4. 碎碎念

以上就是关于某次处理脏业务逻辑的包装记录,本来还打算把晚上学到的 json inline 用法记录说明一下,但是由于本人还没有实践过,那就后面用的时候在继续记录吧。

  • 18岁很好,28岁也不错,38岁可能会更好,只要皱纹不长进心里,我们就永远风华正茂。
  • 一个女人最重要的能力,不是你把自己打扮得多么漂亮,也不是你挣钱有多厉害,而是无论发生任何事情,你都有快乐起来的能力。
  • 当你越来越优秀时,你开始明白,其实每个人都没有好坏之分,没有对错,只有频率不同,做出了不同的选择。有个好心态,路就会走的更宽。

5. 参考资料