在处理 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 true
	default:
		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)
	// 获取所有的 key
	keys := 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

自定义结构类型

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.Name
		tag := 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 main

import (
	"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 string
	B int
	C map[string]int
	D bool
	E *structTest
	F *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、使用和贡献代码。