Golang 关于 JSON 处理遇到的坑

2,215 阅读4分钟

这是我参与8月更文挑战的第13天,活动详情查看:8月更文挑战

看 JSON 处理 代码

import (
   "encoding/json"
   "errors"
   "fmt"
   "reflect"
   "strings"
   "testing"

   jsoniter "github.com/json-iterator/go"
)
// json化
func JsonMarshal(v interface{}) string {
   var fasterJson = jsoniter.ConfigCompatibleWithStandardLibrary
   b, err := fasterJson.Marshal(v)
   if err != nil {
      logs.Error("JsonMarshal failed for v: %+v", v)
      return `{"err_msg": "not json format"}`
   }
   return string(b)
}

// 反json化, jsonStr是json化的字符串,v传空的结构体
func JsonUnmarshal(jsonStr string, v interface{}) error {
   var fasterJson = jsoniter.ConfigCompatibleWithStandardLibrary
   byteValue := []byte(jsonStr)
   err := fasterJson.Unmarshal(byteValue, v)
   if err != nil {
      logs.Error("JsonUnmarshal failed for v: %+v, err = %s", v, err.Error())
      return errors.New("JsonUnmarshal failed for v: %+v")
   }
   return nil
}

字符串为空的处理

json 包在对空字符串的处理,非常容易出错

"" 与”{}"

type User struct {
   Name      string
   FansCount int64
}

func TestJsonMashal3(t *testing.T) {
   str := "{}"
   var user User
   err := JsonUnmarshal(str, &user)

   fmt.Println(err, user)

   str1 := ""
   var user1 User
   err1 := JsonUnmarshal(str1, &user)

   fmt.Println(err1, user1)
}

执行结果:

=== RUN   TestJsonMashal3
<nil> { 0}
JsonUnmarshal failed for v: %+v { 0}
--- PASS: TestJsonMashal3 (0.00s)
PASS

可以看到 在处理 空字符串“” 时 程序抛出异常 JsonUnmarshal failed for v: %+v { 0} 但是如果是 空 json "{}" 发现是可以处理的, 返回的结果是 { 0}

字段为空的处理

type Person struct {
   Name     string
   Age      int64
   Birth    time.Time
   Children []Person
}

func TestJsonMashalEmpty(t *testing.T) {
   person := Person{}

   jsonBytes:= JsonMarshal(person)
   fmt.Println(string(jsonBytes)) //{"Name":"","Age":0,"Birth":"0001-01-01T00:00:00Z","Children":null}
}

执行结果

=== RUN   TestJsonMashal4
{"Name":"","Age":0,"Birth":"0001-01-01T00:00:00Z","Children":null}
--- PASS: TestJsonMashal4 (0.00s)
PASS

发现没,当是 struct 中 的字段没有值时, 使用json.Marshal 方法并不会忽略这些字段,而是输出了他们的默认空值, 这个往往和我们的预期不一致。int 和 float 类型零值是 0,string 类型零值是 "",对象类型零值是 nil。 有没有办法忽略这些空字段。有, 加一个 omitempty tag 就可以了。

type PersonAllowEmpty struct {
   Name     string             `json:",omitempty"`
   Age      int64              `json:",omitempty"`
   Birth    time.Time          `json:",omitempty"`
   Children []PersonAllowEmpty `json:",omitempty"`
}

func TestMarshalEmpty2(t *testing.T) {
   person := PersonAllowEmpty{}
   jsonBytes := JsonMarshal(person)
   fmt.Println(string(jsonBytes)) // {"Birth":"0001-01-01T00:00:00Z"}
}

运行结果

=== RUN   TestMarshalEmpty2
{"Birth":"0001-01-01T00:00:00Z"}
--- PASS: TestMarshalEmpty2 (0.00s)
PASS

字段在任何情况下都被 json 忽略要怎么做?

type Person struct {
	Name     string `json:"-"`
	Age      int64 `json:"-"`
	Birth    time.Time `json:"-"`
	Children []string `json:"-"`
}
 
func main() {
    birth, _ := time.Parse(time.RFC3339, "1988-12-02T15:04:27+08:00")
	person := Person{
		Name: "Wang Wu",
		Age: 30,
		Birth: birth,
		Children: []string{},
	}
 
	jsonBytes, _ := json.Marshal(person)
    fmt.Println(string(jsonBytes))  // {}
}

使用 json:"-" 标签即可忽略所有字段, 输出的结果就是 {} 空 json,不是空字符串。

JSON 处理中精度丢失的问题

反序列化时明确指定了明确的结构体和类型

type User struct {
   Name      string
   FansCount int64
}

// 如果反序列化的时候指定明确的结构体和变量类型
func TestJsonUnmarshal(t *testing.T) {
   const jsonStream = `
        {"name":"ethancai", "fansCount": 9223372036854775807}
    `
   var user User // 类型为User
   err := JsonUnmarshal(jsonStream, &user)
   if err != nil {
      fmt.Println("error:", err)
   }

   fmt.Printf("%+v \n", user)
}

运行结果:

=== RUN   TestJsonUnmarshal
{Name:ethancai FansCount:9223372036854775807} 
--- PASS: TestJsonUnmarshal (0.00s)
PASS

反序列化时不指定结构体类型或者变量,JSON 数据类型,默认是 float64

func TestJsonMarshal(t *testing.T) {
   const jsonStream = `
        {"name":"ethancai", "fansCount": 9223372036854775807}
    `
   var user interface{} // 类型为User
   err := JsonUnmarshal(jsonStream, &user)
   if err != nil {
      fmt.Println("error:", err)
   }
   m := user.(map[string]interface{})

   fansCount := m["fansCount"]

   fmt.Printf("%+v \n", reflect.TypeOf(fansCount).Name())
   fmt.Printf("%+v \n", fansCount.(float64))
}

运行结果:

=== RUN   TestJsonMarshal
float64 
9.223372036854776e+18 
--- PASS: TestJsonMarshal (0.00s)
PASS

可以看到,如果数据精度比较高的时候,反序列化成 float64 类型的数值时存在精度丢失的问题。

JSON 时间格式日期处理问题

func TestTimeProcess(t *testing.T) {
   type Product struct {
      Name      string
      CreatedAt time.Time
   }
   pdt := Product{
      Name:      "Reds",
      CreatedAt: time.Now(),
   }
   b := JsonMarshal(pdt) //{"Name":"Reds","CreatedAt":"2021-08-20T11:03:17.698724+08:00"}

   fmt.Println(b)
}

运行结果

=== RUN   TestTimeProcess
{"Name":"Reds","CreatedAt":"2021-08-20T11:03:17.698724+08:00"}
--- PASS: TestTimeProcess (0.00s)
PASS

JSON的规范中并没有日期类型,不同语言的library对日期序列化的处理也不完全一致, 要特别注意。

结构体字母小写问题

如果机构题字段的首字母是小写, 解析 json 时会失败

type robot struct {
   name   string `json:"name"`
   amount int    `json:"amount"`
}


func TestPareseStruct(t *testing.T) {
   fmt.Println("解析json字符串到单个结构体")
   str := "{"name":"nam1","amount":100}"
   one := robot{}
   err := json.Unmarshal([]byte(str), &one)
   if err != nil {
      fmt.Printf("parse_one(), err=%v", err)
   }
   fmt.Printf("name=%v,amount=%v \n", one.name, one.amount)
}

运行结果

=== RUN   TestPareseStruct
解析json字符串到单个结构体
name=,amount=0 
--- PASS: TestPareseStruct (0.00s)
PASS

需要修改首字母为大写

type robot struct {
    Name   string `json:"name"`
    Amount int    `json:"amount"`
}

func TestPareseStruct(t *testing.T) {
   fmt.Println("解析json字符串到单个结构体")
   str := "{"name":"nam1","amount":100}"
   one := robot{}
   err := json.Unmarshal([]byte(str), &one)
   if err != nil {
      fmt.Printf("parse_one(), err=%v", err)
   }
   fmt.Printf("s=%v",one)
}

运行结果

=== RUN   TestPareseStruct
解析json字符串到单个结构体
s={nam1 100}--- PASS: TestPareseStruct (0.00s)
PASS