1. 背景

众所周知,go中的map是无序的,将json对象反序列化到map中,就丢失了原始的json对象的顺序信息。大多数场景下,这是没问题的。因为json标准中是不要求对象顺序的,但实践中却不一定。

最近在用go重写一个java应用时,遇到了这个问题:原来的java程序返回的数据包含json规格的业务数据,以及一个基于该json串的md5指纹,客户端收到go返回的json数据后,首先验证签名正确,再解析json。go重写的程序返回的数据,数据签名验证始终不通过。经过排查发现,go返回的json与原java应用返回的json中,对象内的key顺序不一致,进而导致了签名验证失败。

在前述的java应用中采用了有序map来存储数据,序列化时保持了顺序,golang中的map和encoding/json包是不保证顺序的。好在encoding/json包中提供了Marshaler/Unmarshaler接口,所以决定尝试自己造轮子,实现在json序列化/反序列化场景下的有序map。

自取地址:https://github.com/bournex/ordered_container

如果要将字符串反序列化到OrderedMap,期望序列化后与原始串一致的话,原始串中不能有空格、制表符等内容哈,json结构必须是紧凑的。原因在反序列化章节里会提到。

2. 实现

整体思路就是自定义个map和array对象,并实现Marshaler/Unmarshaler接口,在MarshalJSON/UnmarshalJSON中进行自定义的json对象顺序编排。

2.1 反序列化

json字符串 -> OrderedMap

json的解析过程符合典型的下推自动机模型,好在encoding/json中已经实现了scanner,并且decoder开放了json token的读取接口,而我们只要使用系统栈结构,稍加应用就可以实现这个PDA。

前面提到了在原始json串中不能有空格,否则OrderedMap序列化后是跟原始串对不上。这是因为encoding/json包的scanner实现是忽略掉这些indent信息的。如果要在序列时还保留这些无效的空格、制表符等内容,我就得自己实现scanner…幸好业务上签名用的json串已经是紧凑的了。

2.2 序列化

OrderedMap -> json字符串

这个就比较简单了,先写入起始token ‘{’,再for/range遍历OrderedMap.Values中的值,对于interface{}类型的值调用json.Marshal,将序列化后的byte数组连续拼接即可,最后拼上结束token ‘}’。OrderedArray的处理过程也是一样的。

3. 用法

package main

import (

"encoding/json"

"fmt"

"github.com/bournex/ordered_container"

)

func main() {

jsonstr := []byte(`{"name":"alice","age":5}`)

var orderedMap ordered_container.OrderedMap

json.Unmarshal(jsonstr, &orderedMap)

b, _ := json.Marshal(orderedMap)

fmt.Println(string(b)) // 输出`{"name":"alice","age":5}`

unorderdMap := map[string]interface{}{}

json.Unmarshal(jsonstr, &unorderdMap)

b2, _ := json.Marshal(unorderdMap)

fmt.Println(string(b2)) // 输出`{"age":5,"name":"alice"}`

}

祝你使用愉快

相关阅读

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。