Go Json 坑

2024-06-08 18:25:31 浏览数 (1)

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的使用
代码语言:javascript复制
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类型的方法。

0 人点赞