Unmarshal精度丢失
代码语言:javascript复制package main
import (
"encoding/json"
"fmt"
"reflect"
)
func main() {
var request = `{"id":7044144249855934983,"name":"demo"}`
var test interface{}
err := json.Unmarshal([]byte(request), &test)
if err != nil {
fmt.Println("error:", err)
}
obj := test.(map[string]interface{})
dealStr, err := json.Marshal(test)
if err != nil {
fmt.Println("error:", err)
}
id := obj["id"]
fmt.Println(string(dealStr))
fmt.Printf("% vn", reflect.TypeOf(id).Name())
fmt.Printf("% vn", id.(float64))
}
打印结果
代码语言:javascript复制{"id":7044144249855935000,"name":"demo"}
float64
7.044144249855935e 18
原因
- 在json的规范中,对于数字类型是不区分整形和浮点型的。
- 在使用json.Unmarshal进行json的反序列化的时候,如果没有指定数据类型,使用interface{}作为接收变量,其默认采用的float64作为其数字的接受类型
- 当数字的精度超过float能够表示的精度范围时就会造成精度丢失的问题
解决方案
- 将id改为string传递
- 使用json.number 类型来避免对float64的使用
package main
import (
"encoding/json"
"fmt"
"strings"
)
func main() {
var request = `{"id":7044144249855934983}`
var test interface{}
decoder := json.NewDecoder(strings.NewReader(request))
decoder.UseNumber()
err := decoder.Decode(&test)
if err != nil {
fmt.Println("error:", err)
}
objStr, err := json.Marshal(test)
if err != nil {
fmt.Println("error:", err)
}
fmt.Println(string(objStr))
}
为什么float64可能出现精度缺失,就必须要搞清楚二进制科学计算法和IEEE754标准的基本原理。
结构体转map[string]interface{} 类型发生变化
代码语言:javascript复制func main() {
u1 := UserInfo{Name: "Rolle", Age: 18}
b, _ := json.Marshal(&u1)
var m map[string]interface{}
_ = json.Unmarshal(b, &m)
for k, v := range m{
fmt.Printf("key:%v value:%v value type:%Tn", k, v, v)
}
}
// key:name value:Rolle value type:string
// key:age value:18 value type:float64
看起来没什么问题,但其实这里是有一个“坑”的。那就是Go语言中的json包在序列化空接口存放的数字类型(整型、浮点型等)都会序列化成float64类型。
也就是上面例子中m["age"]现在底层是一个float64了,不是个int了
解决办法、反射
代码语言:javascript复制// ToMap 结构体转为Map[string]interface{}
func ToMap(in interface{}, tagName string) (map[string]interface{}, error){
out := make(map[string]interface{})
v := reflect.ValueOf(in)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
if v.Kind() != reflect.Struct { // 非结构体返回错误提示
return nil, fmt.Errorf("ToMap only accepts struct or struct pointer; got %T", v)
}
t := v.Type()
// 遍历结构体字段
// 指定tagName值为map中key;字段值为map中value
for i := 0; i < v.NumField(); i {
fi := t.Field(i)
if tagValue := fi.Tag.Get(tagName); tagValue != "" {
out[tagValue] = v.Field(i).Interface()
}
}
return out, nil
}
json 分级解析及数字解析
是否遇到过在无法准确确定json层级关系的情况下对json进行解析的需求
具体的,返回结果有两种情况
第一种
代码语言:javascript复制{
"return": "0",
"result":[
{
"goods_id": 37278077211,
....
"shop_name": "xxxxx",
}
]
}
第二种
代码语言:javascript复制{
"return": "0",
"result": {
"data": [
{
"goods_id": 58054798450,
......
"shop_name": "xxxxxxxx"
}
]
}
}
由于在解析前我们并不能确定result到底是一个struct还是一个Slice,因此我们也无法直接利用json.Unmarshal一步解出对应的struct对象。好在我们知道所有json都可以直接解析成map[string]interface{}的结构,因此我们可以将json先转化为map,然后根据结构名key去决定后续的转换流程,具体代码如下:
解决方案
将json直接解析为map
代码语言:javascript复制var object interface{}
var data interface{}
err := json.Unmarshal([]byte(jsonStr),&object)
if err != nil{
fmt.Printf("unmarshal %s error: %sn",jsonStr,err.Error())
}
//判断returnCode
ret := object.(map[string]interface{})["return"]
if ret != 0{
fmt.Println("the response of http error")
}
//判断result是何种类型
result := object.(map[string]interface{})["result"]
resultType := reflect.TypeOf(result)
if resultType.Kind() == reflect.Map{
data = result.(map[string]interface{})["data"]
}
if resultType.Kind() == reflect.Slice{
data = result
}
//解析goods_id
var skuList []int64
for _,v := range data.([]interface{}){
preSku := v.(map[string]interface{})["goods_id"].(float64)
skuList = append(skuList,int64(preSku))
}
fmt.Printf("the skuLst = % vn",skuList)
这种方式的优点是只需要Unmarshal一次,缺点是每一级都需要显示的去做类型转化,书写起来比较繁琐。尤其是json本身结构复杂,其中只有一小部分需要确定具体类型的情况下,解析过程会更加繁琐复杂.
是否可以只解析确定部分,不确定的部分先保留[]byte的原始格式,按map解析
这时候就需要用到json.RawMessage字段类型
在解析json过程中,有时可能只需要解析json的某一部分数据,比如,当json中只有一部分是需要的数据,或者需要先解析一部分数据,才能根据解析的部分数据来决定剩余数据如何解析。继续以上面的需求为例。此时需要预先定义需要解析的部分
代码语言:javascript复制type RespStruct struct {
RetCode int `json:"return"`
Result json.RawMessage `json:"result"`
}
首先解析return字段。result字段内容将继续保持[]byte类型的状态。接下来继续解析剩余部分
代码语言:javascript复制var object RespStruct
err := json.Unmarshal([]byte(jsonStr2),&object)
if err != nil{
fmt.Printf("unmarshal %s error: %sn",jsonStr,err.Error())
}
//判断returnCode
if object.RetCode != 0{
fmt.Println("the response of http error")
}
//判断result是何种类型
var data interface{}
err = json.Unmarshal(object.Result,&data)
if err != nil{
fmt.Printf("unmarshal %s error: %sn",object.Result,err.Error())
}
resultType := reflect.TypeOf(data)
if resultType.Kind() == reflect.Map{
data = data.(map[string]interface{})["data"]
}
//解析goods_id
var skuList []int64
for _,v := range data.([]interface{}){
preSku := v.(map[string]interface{})["goods_id"].(float64)
skuList = append(skuList,int64(preSku))
}
fmt.Printf("the skuLst = % vn",skuList)
json.Number类型的使用
goods_id字段的类型先由interface{}类型转为float64,然后才被转换为需要的int64呢?
这是因为在 json 中是没有整型和浮点型之分的,当利用json 包中的 Unmarshal 方法将数字类型解析为interface{}时,它就会将把所有数字类型全部转换为和规范最接近的float64类型。如果希望更加方便的将数字类型准换为指定的类型,就需要用到json.Number这个类型。具体如下:
代码语言:javascript复制var object RespStruct
err := json.Unmarshal([]byte(jsonStr),&object)
if err != nil{
fmt.Printf("unmarshal %s error: %sn",jsonStr,err.Error())
}
//判断returnCode
if object.RetCode != 0{
fmt.Println("the response of http error")
}
//判断result是何种类型
var data interface{}
decoder := json.NewDecoder(bytes.NewReader(object.Result))
decoder.UseNumber()
decoder.Decode(&data)
resultType := reflect.TypeOf(data)
if resultType.Kind() == reflect.Map{
data = data.(map[string]interface{})["data"]
}
//解析goods_id
var skuList []int64
for _,v := range data.([]interface{}){
preSku,err := v.(map[string]interface{})["goods_id"].(json.Number).Int64()
if err != nil{
fmt.Printf("get goods_id error")
}
skuList = append(skuList,preSku)
}
fmt.Printf("the skuLst = % vn",skuList)
json.Number本身是string类型,只是在json包中被定义了别名,然后通过封装的三个方法,实现了将string转换为int64和float64类型的方法。