用 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 序列化器相关推荐

  1. 基于vue手写一个分屏器,通过鼠标控制屏幕宽度。

    基于vue手写一个分屏器,通过鼠标控制屏幕宽度. 先来看看实现效果: QQ录屏20220403095856 下面是实现代码: <template><section class=&qu ...

  2. ElasticSearch——手写一个ElasticSearch分词器(附源码)

    1. 分词器插件 ElasticSearch提供了对文本内容进行分词的插件系统,对于不同的语言的文字分词器,规则一般是不一样的,而ElasticSearch提供的插件机制可以很好的集成各语种的分词器. ...

  3. 手写一个JSON反序列化程序

    上一篇文章<JSON是什么>给大家介绍了JSON的标准规范,今天就自己动手写一个JSON的反序列化程序,并命名它为 zjson. 0 开始之前 本篇文章的目的是学习实践,所以我们选择相对简 ...

  4. django 不包括字段 序列化器_手写一个Django序列化功能

    本文章的代码已上传至github上(github包含了更多功能,相关文章后续更新) AGL1994/django-building​github.com 前言 目前Django比较知名的序列化框架有D ...

  5. erlang xml 解析_用yecc(erlang)写一个json解析器

    昨天写了个json的解析器.其实yecc早看过了,只是那时对自己要求太高,想一下子写个小语言.然后大脑就陷入混乱... 后来注意力转移了.就不那么急着去开发些难道大的.今天回来一看,觉得都理解了,实践 ...

  6. 手写一个json格式化 api

    最近写的一个东西需要对json字符串进行格式化然后显示在网页上面. 我就想去网上找找有没有这样的api可以直接调用.百度 json api ,搜索结果都是那种只能在网页上进行校验的工具,没有api. ...

  7. 未能加载文件或程序集或它的某一个依赖项_手写一个miniwebpack

    前言 之前好友希望能介绍一下 webapck 相关的内容,所以最近花费了两个多月的准备,终于完成了 webapck 系列,它包括一下几部分: webapck 系列一:手写一个 JavaScript 打 ...

  8. 手把手教你实现一个 JSON 解析器!

    1. 背景 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式.相对于另一种数据交换格式 XML,JSON 有着诸多优点.比如易读性更好,占用空间更少等. 在 ...

  9. json解析对应的value为null_徒手撸一个JSON解析器

      Java大联盟 致力于最高效的Java学习 关注 作者 | 田小波 cnblogs.com/nullllun/p/8358146.html1.背景JSON(JavaScript Object No ...

最新文章

  1. 触发器实现两表之间的INSERT,DELETE,UPDATE
  2. Python 基础 一
  3. 写给未来程序员的建议
  4. Xamarin Android设置界面提示类型错误
  5. ASM心跳超时检测之--Delayed ASM PST heart beats
  6. python sklearn.datasets.fetch_mldata MNIST手写数字数据集无法获取, 报错 Function fetch_mldata is deprecated 的解决办法
  7. python无师自通配套资源_Python编程 无师自通 专业程序员的养成
  8. 子网掩码相关教学 子网掩码快速算法 沉睡不醒blog
  9. 网盘是否能做一只安全的企业信息快递手
  10. unittest-常见问题解决方案记录
  11. java hdfs 指定用户目录_HDFS目录(文件 )权限管理
  12. axios config里自定义属性,使用拦截器拦截,无法拿到自定义属性问题
  13. Netty 源码解析系列-服务端启动流程解析
  14. linux普通用户id一般是,实际用户ID和有效用户ID (一) *****
  15. python进程之间修改数据[Manager]与进程池[Pool]
  16. 快速计算代码行小工具
  17. 【操作系统】_7种进程调度算法
  18. beetl 页面标签_高级用法 · Beetl3官方文档 · 看云
  19. var模型的建模步骤python_Python语言之概述建模步骤
  20. Android UI设计之十自定义ListView,实现QQ空间阻尼下拉刷新和渐变菜单栏效果

热门文章

  1. 关于vue弹窗自定义
  2. 台式计算机怎么加一个硬盘,台式电脑硬盘怎么多安装一个?电脑安装多加一块硬盘的方法...
  3. 基于`IRIS`,动态解析`HL7`消息
  4. python的if-else语法
  5. [UML]建模是什么?为什么要建模?
  6. ios wifi 定位_iOS开发Wifi 定位原理及iOS Wifi 列表获取
  7. 计算机32位如何变成64位,我电脑是32位的
  8. android中drawable的自建资源
  9. DVWA-文件上传与文件包含
  10. XP sp3 安装Step7 V5.5和WinCC V7.0记录(仅用于个人)