前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go Json 坑

Go Json 坑

原创
作者头像
Michel_Rolle
发布2024-06-08 18:25:31
5020
发布2024-06-08 18:25:31
举报
文章被收录于专栏:golang分享golang分享

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("%+v\n", reflect.TypeOf(id).Name())
	fmt.Printf("%+v\n", 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:%T\n", 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: %s\n",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 = %+v\n",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: %s\n",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: %s\n",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 = %+v\n",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: %s\n",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 = %+v\n",skuList)

json.Number本身是string类型,只是在json包中被定义了别名,然后通过封装的三个方法,实现了将string转换为int64和float64类型的方法。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Unmarshal精度丢失
  • 结构体转map[string]interface{} 类型发生变化
  • json 分级解析及数字解析
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档