Golang 的 unmarshal 踩坑指南

devtools/2024/10/18 7:49:07/

文章目录

    • 1. 写在最前面
    • 2. 字段区分出空字段还是未设置字段
      • 2.1 问题描述
      • 2.2 解决
    • 3. 字段支持多种类型 & 按需做不同类型处理
      • 3.1 问题描述
      • 3.2 解决
    • 4. 碎碎念
    • 5. 参考资料

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 mainimport ("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 Messageerr := 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 mainimport ("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 Messageerr := 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 mainimport ("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 fileListfmt.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 Messageerr := 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. 参考资料

  • Golang 的 “omitempty” 关键字略解

  • Golang 中使用 JSON 时如何区分空字段和未设置字段?


http://www.ppmy.cn/devtools/42040.html

相关文章

2D Chests Assets - Mega Pack

科幻/奇幻/经典主题的箱子和容器。AAA质量,高分辨率,VFX,源PSD文件。 这是一个带有手绘套装的大包装: -【梦幻之栗】 -【科幻钱包】 AAA质量。高分辨率。一切都已准备就绪,可供使用。包括PSD文件。 在1.1版本中添加了VFX并将项目更新为URP。请注意,新的VFX仅适用于URP/HD…

【考研数学】进入强化,基础过关《660》不会做怎么办?

做题没思路,说明学习的过程中走了弯路 很多人,按部就班的学习,觉得课我也听了,讲义也看了,怎么别人做题很顺,自己翻开书就一头雾水。搞清楚其中的差别,也就解决了做题没思路的问题。 首先我们…

RustGUI学习(iced/iced_aw)之小部件(十九):如何使用context_menu部件来创建右击菜单?

前言 本专栏是学习Rust的GUI库iced的合集,将介绍iced涉及的各个小部件分别介绍,最后会汇总为一个总的程序。 iced是RustGUI中比较强大的一个,目前处于发展中(即版本可能会改变),本专栏基于版本0.12.1. 概述 这是本专栏的第十九篇,主要讲述context_menu右击菜单部件的使…

Rancher-Kubewarden-保姆级教学-含Demo测试

一、什么是Kubewarden? What is Kubewarden? | Kubewarden 1、就是容器集群的准入策略引擎。 1、使用的策略其实就是k8s原生的security context. 2、使用WebAssembly来编写策略。 1、WebAssembly,可以使用擅长的开发语言来编写策略。(下面的…

webpack优化构建速度示例-合理配置loader的include exclude:

实际上,babel-loader 在 Webpack 配置中默认并不包含 exclude 和 include 选项的默认值,通常,为了优化构建性能,开发者会显式地设置 exclude 和 include 选项,以便 babel-loader 只处理必要的文件。 src/index.js impo…

大模型微调之 在亚马逊AWS上实战LlaMA案例(九)

大模型微调之 在亚马逊AWS上实战LlaMA案例(九) 代码阅读 src/llama_recipes/inference/prompt_format_utils.py 这段代码是一个Python模块,它定义了几个类和模板,用于生成安全评估的提示文本。以下是对每一行代码的注释和提示词…

[算法][数组][leetcode]2391. 收集垃圾的最少总时间

题目地址: https://leetcode.cn/problems/minimum-amount-of-time-to-collect-garbage/description/ 题解: class Solution {public int garbageCollection(String[] garbage, int[] travel) {int ans 0;//先计算收所有的垃圾需要多少时间for(String s :garbage){…

设计模式-08 - 模板方法模式 Template Method

设计模式-08 - 模板方法模式 Template Method 1.定义 模板方法模式是一种设计模式,它定义了一个操作的骨架,而由子类来决定如何实现该操作的某些步骤。它允许子类在不改变算法结构的情况下重定义算法的特定步骤。 模板方法模式适合用于以下情况&am…