用 Go 手写一个 JSON 序列化器
用 Go 手写一个 JSON 序列化器
- 方案
- 实现
- 字符串转义
- 忽略类型
- 序列化器主体
- 数字和逻辑类型
- 字符串类型
- 数组类型
- 字典类型
- 自定义结构类型
- 指针类型
- API
- 使用
- 安装
- 调用
- 测试
- 开源和贡献
在处理 JSON 的时候,序列化是一个非常有用的功能,可以直接将对象转换为序列化后的 JSON。
一般来说,处理 JSON 的序列化时的步骤是递归遍历一个对象中的所有字段,然后根据其数据类型生成指定格式的 JSON 数据。
方案
Go 语言中,主要有如下数据的类型:
- 数字类型:Int、Float32等等
- 逻辑类型:Bool
- 字符串类型:String
- 数组类型:Array、Slice
- 字典类型:Map
- 自定义数据类型:Struct
- 以上数据类型的指针
因此只需要根据以上类型分别进行处理即可,而遍历对象中的所有字段可以通过反射来完成。
实现
首先我们需要实现一些工具函数。
字符串转义
在生成 JSON 字符串时,需要将换行以及双引号转义,以免引起冲突。
func esacpeString(s string) string {// 将 \n \r " 转义return strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(s, "\n", "\\n"), "\r", "\\r"), "\"", "\\\"")
}
忽略类型
有一些类型是不需要生成 JSON 的,例如函数类型,因此编写这样一个函数用来判断类型并返回是否跳过序列化。
func skipParsing(kind reflect.Kind) bool {switch kind {case reflect.Chan, reflect.Complex128, reflect.Complex64, reflect.Func, reflect.Invalid:return truedefault:return false}
}
序列化器主体
现在开始实现序列化器。
首先,如果数据是 null,则需要直接返回 null 作为序列化结果:
if v == nil {return "null", nil
}
然后需要检测类型并判断是否需要调过:
t := reflect.TypeOf(v)
kind := t.Kind()
if skipParsing(kind) {return "", nil
}
如果类型不需要被跳过的话,就要根据具体的类型进行序列化了,可以通过 switch
语句进行类型的判断:
数字和逻辑类型
直接格式化返回即可。
case reflect.Int, reflect.Int8, reflect.Int16,reflect.Int32, reflect.Int64, reflect.Uint,reflect.Uint8, reflect.Uint16, reflect.Uint32,reflect.Uint64, reflect.Float32,reflect.Float64, reflect.Bool:return fmt.Sprintf("%v", v), nil
字符串类型
返回转义后的字符串。
case reflect.String:return "\"" + esacpeString(v.(string)) + "\"", nil
数组类型
数组类型稍微复杂一些,需要新建一个数组用来保存每个元素序列化后的数据,然后对数组中的每一个元素单独进行序列化。
case reflect.Array, reflect.Slice:vArray := reflect.ValueOf(v)len := vArray.Len()results := make([]string, len)// 对数组中每个元素单独进行序列化for i := 0; i < len; i++ {result, err := parseJSON(vArray.Index(i).Interface())if err != nil {return "", err}results[i] = result}// 最后用 "," 将每个结果连接起来return fmt.Sprintf("[%v]", strings.Join(results, ",")), nil
字典类型
字典类型需要遍历所有的 key,将 key 作为 JSON 的 key,再将 value 进行一次序列化。
case reflect.Map:vMap := reflect.ValueOf(v)// 获取所有的 keykeys := vMap.MapKeys()len := len(keys)results := make([]string, len)j := 0// 遍历每一个 key 对应的元素for i, key := range keys {val := vMap.MapIndex(key)result := "null"// 如果 value 有效if val.IsValid() {if !val.CanInterface() {j++continue}// 对 value 执行序列化操作r, err := parseJSON(val.Interface())if err != nil {return "", err}result = r}// 使用 "key": value 的格式拼接单个结果results[i-j] = "\"" + esacpeString(fmt.Sprintf("%v", key)) + "\"" + ":" + result}// 最后将所有结果通过 "," 连接return fmt.Sprintf("{%v}", strings.Join(results[0:len-j], ",")), nil
自定义结构类型
自定义结构类型与字典类型类似,不同的是获取 key 变成了获取结构中的字段名称。考虑到用户可能需要为字段单独指定序列化后的名称,因此这里添加了根据 mytag
的值来覆盖原字段名的功能。
case reflect.Struct:value := reflect.ValueOf(v)num := value.NumField()results := make([]string, num)j := 0// 遍历每一个字段for i := 0; i < num; i++ {vField := value.Field(i)tField := value.Type().Field(i)// 判断是否有效if !vField.CanInterface() || skipParsing(tField.Type.Kind()) {j++continue}// 获取字段名称和 tag 名称key := tField.Nametag := tField.Tag.Get("mytag")if tag != "" {key = tag}// 对值进行序列化result := "null"f := vField.Interface()val := reflect.ValueOf(f)if val.IsValid() {r, err := parseJSON(val.Interface())if err != nil {return "", err}result = r}// 使用 "name": value 的格式拼接结果results[i-j] = "\"" + esacpeString(key) + "\"" + ":" + result}// 最后将所有结果通过 "," 连接return fmt.Sprintf("{%v}", strings.Join(results[0:num-j], ",")), nil
指针类型
指针类型较为简单,只需要将其解引用后对实际数据进行序列化即可。
case reflect.Ptr, reflect.Interface:val := reflect.ValueOf(v).Elem()// 判断是否有效if !val.IsValid() {return "null", nil}// 序列化解引用后的值return parseJSON(val.Interface())
}
至此所有序列化功能就编写结束了
API
最后,提供两个 API 允许用户调用。
// JSONMarshal 将对象序列化为 JSON 字节流
func JSONMarshal(v interface{}) ([]byte, error) {result, err := parseJSON(v)if err != nil {return nil, err}return []byte(result), nil
}// JSONMarshalAsString 将对象序列化为 JSON 字符串
func JSONMarshalAsString(v interface{}) (string, error) {result, err := parseJSON(v)return result, err
}
使用
安装
go get -u gitee.com/hez2010/hjson
调用
package mainimport ("fmt"// 引入库"gitee.com/hez2010/hjson"
)// 声明一个结构
type example struct {A map[string]int `mytag:"map"` // 使用 mytag 指定名称B bool `mytag:"bool"`C int `mytag:"int"`D string `mytag:"string"`E *bool // 指针类型也支持
}func main() {m := map[string]int{"A": 1,}e := example{m, false, 1, "hello", nil}// 序列化为字符串result, _ := hjson.JSONMarshalAsString(e)fmt.Println(result)
}
输出:
{"map":{"A":1},"bool":false,"int":1,"string":"hello","E":null}
可以使用 mytag
指定序列化后的字段名称。
测试
// 测试数组
func TestArray(t *testing.T) {expected := "[1,2,3,4,5]"result, err := parseJSON([]int{1, 2, 3, 4, 5})if err != nil {t.Error(err)}if result != expected {t.Errorf("expected: %v, actual: %v", expected, result)}
}// 测试数字
func TestNumber(t *testing.T) {expected := "6"result, err := parseJSON(6)if err != nil {t.Error(err)}if result != expected {t.Errorf("expected: %v, actual: %v", expected, result)}
}// 测试字符串
func TestString(t *testing.T) {expected := "\"hell\\no\""result, err := parseJSON("hell\no")if err != nil {t.Error(err)}if result != expected {t.Errorf("expected: %v, actual: %v", expected, result)}
}// 测试布尔值
func TestBool(t *testing.T) {expected := "true"result, err := parseJSON(true)if err != nil {t.Error(err)}if result != expected {t.Errorf("expected: %v, actual: %v", expected, result)}
}// 测试 null
func TestNil(t *testing.T) {expected := "null"result, err := parseJSON(nil)if err != nil {t.Error(err)}if result != expected {t.Errorf("expected: %v, actual: %v", expected, result)}
}// 测试字典
func TestMap(t *testing.T) {expected1 := `{"a":1,"b":2}`expected2 := `{"b":2,"a":1}`result, err := parseJSON(map[string]int{"a": 1, "b": 2})if err != nil {t.Error(err)}if result != expected1 && result != expected2 {t.Errorf("expected: %v, actual: %v", expected1, result)}
}// 测试指针
func TestPointer(t *testing.T) {expected1 := `{"a":1,"b":2}`expected2 := `{"b":2,"a":1}`result, err := parseJSON(&map[string]int{"a": 1, "b": 2})if err != nil {t.Error(err)}if result != expected1 && result != expected2 {t.Errorf("expected: %v, actual: %v", expected1, result)}
}type structTest struct {A stringB intC map[string]intD boolE *structTestF *structTest
}// 测试自定义的复杂结构
func TestStruct(t *testing.T) {s1 := structTest{"hello", 1, map[string]int{"a": 1, "b": 2}, false, nil, nil}s2 := structTest{"hello", 1, map[string]int{"a": 1, "b": 2}, false, nil, &s1}expected1 := `{"A":"hello","B":1,"C":{"a":1,"b":2},"D":false,"E":null,"F":{"A":"hello","B":1,"C":{"a":1,"b":2},"D":false,"E":null,"F":null}}`expected2 := `{"A":"hello","B":1,"C":{"b":2,"a":1},"D":false,"E":null,"F":{"A":"hello","B":1,"C":{"a":1,"b":2},"D":false,"E":null,"F":null}}`expected3 := `{"A":"hello","B":1,"C":{"a":1,"b":2},"D":false,"E":null,"F":{"A":"hello","B":1,"C":{"b":2,"a":1},"D":false,"E":null,"F":null}}`expected4 := `{"A":"hello","B":1,"C":{"b":2,"a":1},"D":false,"E":null,"F":{"A":"hello","B":1,"C":{"b":2,"a":1},"D":false,"E":null,"F":null}}`result, err := parseJSON(s2)if err != nil {t.Error(err)}if result != expected1 && result != expected2 && result != expected3 && result != expected4 {t.Errorf("expected: %v, actual: %v", expected1, result)}
}type tagTest struct {A string `mytag:"tag1"`B int `mytag:"tag2"`
}// 测试自定义 Tag
func TestTag(t *testing.T) {s := tagTest{"hello", 1}expected := `{"tag1":"hello","tag2":1}`result, err := parseJSON(s)if err != nil {t.Error(err)}if result != expected {t.Errorf("expected: %v, actual: %v", expected, result)}
}
最后运行测试:
go test
得到如下输出:
PASS
ok gitee.com/hez2010/hjson 0.635s
所有测试全部通过了。
开源和贡献
最后,所有代码放到 https://gitee.com/hez2010/hjson 并以 MIT 协议开源,欢迎各位 star、使用和贡献代码。
用 Go 手写一个 JSON 序列化器相关推荐
- 基于vue手写一个分屏器,通过鼠标控制屏幕宽度。
基于vue手写一个分屏器,通过鼠标控制屏幕宽度. 先来看看实现效果: QQ录屏20220403095856 下面是实现代码: <template><section class=&qu ...
- ElasticSearch——手写一个ElasticSearch分词器(附源码)
1. 分词器插件 ElasticSearch提供了对文本内容进行分词的插件系统,对于不同的语言的文字分词器,规则一般是不一样的,而ElasticSearch提供的插件机制可以很好的集成各语种的分词器. ...
- 手写一个JSON反序列化程序
上一篇文章<JSON是什么>给大家介绍了JSON的标准规范,今天就自己动手写一个JSON的反序列化程序,并命名它为 zjson. 0 开始之前 本篇文章的目的是学习实践,所以我们选择相对简 ...
- django 不包括字段 序列化器_手写一个Django序列化功能
本文章的代码已上传至github上(github包含了更多功能,相关文章后续更新) AGL1994/django-buildinggithub.com 前言 目前Django比较知名的序列化框架有D ...
- erlang xml 解析_用yecc(erlang)写一个json解析器
昨天写了个json的解析器.其实yecc早看过了,只是那时对自己要求太高,想一下子写个小语言.然后大脑就陷入混乱... 后来注意力转移了.就不那么急着去开发些难道大的.今天回来一看,觉得都理解了,实践 ...
- 手写一个json格式化 api
最近写的一个东西需要对json字符串进行格式化然后显示在网页上面. 我就想去网上找找有没有这样的api可以直接调用.百度 json api ,搜索结果都是那种只能在网页上进行校验的工具,没有api. ...
- 未能加载文件或程序集或它的某一个依赖项_手写一个miniwebpack
前言 之前好友希望能介绍一下 webapck 相关的内容,所以最近花费了两个多月的准备,终于完成了 webapck 系列,它包括一下几部分: webapck 系列一:手写一个 JavaScript 打 ...
- 手把手教你实现一个 JSON 解析器!
1. 背景 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式.相对于另一种数据交换格式 XML,JSON 有着诸多优点.比如易读性更好,占用空间更少等. 在 ...
- json解析对应的value为null_徒手撸一个JSON解析器
Java大联盟 致力于最高效的Java学习 关注 作者 | 田小波 cnblogs.com/nullllun/p/8358146.html1.背景JSON(JavaScript Object No ...
最新文章
- 触发器实现两表之间的INSERT,DELETE,UPDATE
- Python 基础 一
- 写给未来程序员的建议
- Xamarin Android设置界面提示类型错误
- ASM心跳超时检测之--Delayed ASM PST heart beats
- python sklearn.datasets.fetch_mldata MNIST手写数字数据集无法获取, 报错 Function fetch_mldata is deprecated 的解决办法
- python无师自通配套资源_Python编程 无师自通 专业程序员的养成
- 子网掩码相关教学 子网掩码快速算法 沉睡不醒blog
- 网盘是否能做一只安全的企业信息快递手
- unittest-常见问题解决方案记录
- java hdfs 指定用户目录_HDFS目录(文件 )权限管理
- axios config里自定义属性,使用拦截器拦截,无法拿到自定义属性问题
- Netty 源码解析系列-服务端启动流程解析
- linux普通用户id一般是,实际用户ID和有效用户ID (一) *****
- python进程之间修改数据[Manager]与进程池[Pool]
- 快速计算代码行小工具
- 【操作系统】_7种进程调度算法
- beetl 页面标签_高级用法 · Beetl3官方文档 · 看云
- var模型的建模步骤python_Python语言之概述建模步骤
- Android UI设计之十自定义ListView,实现QQ空间阻尼下拉刷新和渐变菜单栏效果
热门文章
- 关于vue弹窗自定义
- 台式计算机怎么加一个硬盘,台式电脑硬盘怎么多安装一个?电脑安装多加一块硬盘的方法...
- 基于`IRIS`,动态解析`HL7`消息
- python的if-else语法
- [UML]建模是什么?为什么要建模?
- ios wifi 定位_iOS开发Wifi 定位原理及iOS Wifi 列表获取
- 计算机32位如何变成64位,我电脑是32位的
- android中drawable的自建资源
- DVWA-文件上传与文件包含
- XP sp3 安装Step7 V5.5和WinCC V7.0记录(仅用于个人)