Go json 反序列化成 interface{} 对 Number 的处理


json 的规范中,对于数字类型,并不区分是整型还是浮点型。
go json number.gif
对于如下 json 文本:

{
    "name": "ethancai",
    "fansCount": 9223372036854775807
}

如果反序列化的时候指定明确的结构体和变量类型

package main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    Name      string
    FansCount int64
}

func main() {
    const jsonStream = `{"name":"ethancai", "fansCount": 9223372036854775807}`
    var user User  // 类型为User
    err := json.Unmarshal([]byte(jsonStream), &user)
    if err != nil {
        fmt.Println("error:", err)
    }
    fmt.Printf("%+v \n", user)
}

Output:

{Name:ethancai FansCount:9223372036854775807}

如果反序列化不指定结构体类型或者变量类型,则 json 中的数字类型,默认被反序列化成 float64 类型:

package main

import (
    "encoding/json"
    "fmt"
    "reflect"
)

func main() {
    const jsonStream = `{"name":"ethancai", "fansCount": 9223372036854775807}`
    // 不指定反序列化的类型
    var user interface{}
    err := json.Unmarshal([]byte(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))
}

Output:

float64
9.223372036854776e+18

另一个程序

package main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    Name      string
    // 不指定FansCount变量的类型
    FansCount interface{}
}

func main() {
    const jsonStream = `{"name":"ethancai", "fansCount": 9223372036854775807}`
    var user User
    err := json.Unmarshal([]byte(jsonStream), &user)
    if err != nil {
        fmt.Println("error:", err)
    }
    fmt.Printf("%+v \n", user)
}

Output:

{Name:ethancai FansCount:9.223372036854776e+18}

从上面的程序可以发现,如果 fansCount 精度比较高,反序列化成 float64 类型的数值时存在丢失精度的问题。

package main

import (
    "encoding/json"
    "fmt"
    "reflect"
    "strings"
)

func main() {
    const jsonStream = `{"name":"ethancai", "fansCount": 9223372036854775807}`
    decoder := json.NewDecoder(strings.NewReader(jsonStream))
    // UseNumber causes the Decoder to unmarshal a number into an interface{} as a Number instead of as a float64.
    decoder.UseNumber()
    var user interface{}
    if err := decoder.Decode(&user); err != nil {
        fmt.Println("error:", err)
        return
    }
    m := user.(map[string]interface{})
    fansCount := m["fansCount"]
    fmt.Printf("%+v \n", reflect.TypeOf(fansCount).PkgPath() + "." + reflect.TypeOf(fansCount).Name())
    v, err := fansCount.(json.Number).Int64()
    if err != nil {
        fmt.Println("error:", err)
        return
    }
    fmt.Printf("%+v \n", v)
}

Output:

encoding/json.Number
9223372036854775807

上面的程序,使用了 func (*Decoder) UseNumber 方法告诉反序列化 json 的数字类型的时候,不要直接转换成 float64,而是转换成 json.Number 类型。
json.Number内部实现机制:

// A Number represents a JSON number literal.
type Number string

// String returns the literal text of the number.
func (n Number) String() string { return string(n) }

// Float64 returns the number as a float64.
func (n Number) Float64() (float64, error) {
    return strconv.ParseFloat(string(n), 64)
}

// Int64 returns the number as an int64.
func (n Number) Int64() (int64, error) {
    return strconv.ParseInt(string(n), 10, 64)
}

json.Number 本质是字符串,反序列化的时候将 json 的数值先转成 json.Number,其实是一种延迟处理的手段,待后续逻辑需要时候,再把 json.Number 转成 float64 或者 int64。

阅读全文

Go json 库 json-iterator


高效json库.png

直接替换
json.Marshal 替为 jsoniter.Marshal

type ColorGroup struct {
    ID     int
    Name   string
    Colors []string
}
group := ColorGroup{
    ID:     1,
    Name:   "Reds",
    Colors: []string{"Crimson", "Red", "Ruby", "Maroon"},
}
b, err := jsoniter.Marshal(group)

阅读全文

Go map struct 相互转换


map to struct

import (
    "testing"
    "github.com/goinggo/mapstructure"
)

func TestMap2Struct(t *testing.T) {
    mapInstance := make(map[string]interface{})
    mapInstance["Name"] = "liang637210"
    mapInstance["Age"] = 28
    var person Person
    // 将 map 转换为指定的结构体
    if err := mapstructure.Decode(mapInstance, &person); err != nil {
        t.Fatal(err)
    }
    t.Logf("map2struct后得到的 struct 内容为:%v", person)
}

struct to map

import (
    "testing"
    "reflect"
)

type User struct {
    Id        int    `json:"id"`
    Username    string    `json:"username"`
    Password    string    `json:"password"`
}

func Struct2Map(obj interface{}) map[string]interface{} {
    t := reflect.TypeOf(obj)
    v := reflect.ValueOf(obj)
    var data = make(map[string]interface{})
    for i := 0; i < t.NumField(); i++ {
        data[t.Field(i).Name] = v.Field(i).Interface()
    }
    return data
}

func TestStruct2Map(t *testing.T) {
    user := User{5, "zhangsan", "password"}
    data := Struct2Map(user)
    t.Logf("struct2map得到的map内容为:%v", data)
}
阅读全文

Go 处理 json


json to struct

import (
    "testing"
    "encoding/json"
)

// 这里对应的 N 和 A 不能为小写,首字母必须为大写,这样才可对外提供访问,具体 json 匹配是通过后面的 tag 标签进行匹配的,与 N 和 A 没有关系
// tag 标签中 json 后面跟着的是字段名称,都是字符串类型,要求必须加上双引号
type Person struct {
    N string     `json:"name"`
    A int        `json:"age"`
}

func TestStruct2Json(t *testing.T) {
    jsonStr := `{
        "name":"liangyongxing",
        "age":12
    }`
    var person Person
    json.Unmarshal([]byte(jsonStr), &person)
    t.Log(person)
}

阅读全文

Go Slice 底层实现


在 Go 中,与 C 数组变量隐式作为指针使用不同,Go 数组是值类型,赋值和函数传参操作都会复制整个数组数据。

func main() {
    arrayA := [2]int{100, 200}
    var arrayB [2]int

    arrayB = arrayA

    fmt.Printf("arrayA : %p , %v\n", &arrayA, arrayA)
    fmt.Printf("arrayB : %p , %v\n", &arrayB, arrayB)

    testArray(arrayA)
}

func testArray(x [2]int) {
    fmt.Printf("func Array : %p , %v\n", &x, x)
}

打印结果:

arrayA : 0xc4200bebf0 , [100 200]  
arrayB : 0xc4200bec00 , [100 200]  
func Array : 0xc4200bec30 , [100 200]

阅读全文

Go 单元测试


Go 语言中自带有一个轻量级的测试框架 testing 和自带的 go test 命令来实现单元测试和性能测试。
实际操作就是建立一个 gotest 目录。在该目录下面创建两个文件:gotest.go 和 gotest_test.go。
gotest.go 这个文件里面我们是创建了一个包,里面有一个函数实现了除法运算:

package gotest

import (
    "errors"
)
// 除法函数
func Division(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("除数不能为0")
    }
    return a / b, nil
}

gotest_test.go 这是我们的单元测试文件,但是记住下面的这些原则:

  • 文件名必须是 _test.go 结尾的,这样在执行 go test 的时候才会执行到相应的代码。
  • 你必须 import testing 这个包。
  • 所有的测试用例函数必须是 Test 开头。
  • 测试用例会按照源代码中写的顺序依次执行。
  • 测试函数 TestXxx() 的参数是 testing.T,我们可以使用该类型来记录错误或者是测试状态。

测试格式:
func TestXxx (t *testing.T),Xxx 部分可以为任意的字母数字的组合,但是首字母不能是小写字母 [a-z],例如
Testintdiv 是错误的函数名。函数中通过调用 testing.T 的 Error,Errorf,FailNow,Fatal,FatalIf 方法,说明测试不通过,调用 Log 方法用来记录测试的信息。





阅读全文

Go 比较两个 Slice 是否相等


开发中经常会遇到需要比较两个 slice 包含的元素是否完全相等的情况,一般来说有两个思路:

  • reflect 比较的方法
  • 循环遍历比较的方法

reflect 比较的方法

func StringSliceReflectEqual(a, b []string) bool {
    return reflect.DeepEqual(a, b)
}

这个写法很简单,就是直接使用 reflect 包的 reflect.DeepEqual 方法来比较 a 和 b 是否相等。

阅读全文

Go Redis 库 Redigo


https://github.com/garyburd/redigo/

引入库

import (
    "github.com/garyburd/redigo/redis"
)

查询数据

cli, cerr := redis.Dial("tcp", ip:port, redis.DialPassword(redisPasswd), redis.DialDatabase(1))
if cerr != nil {
    fmt.Println(cerr)
}
defer cli.Close()
// 查询 key
val, err := redis.String(cli.Do("GET", key))
if err != nil {
    fmt.Println(err)
}
return val

写入数据

cli, cerr := redis.Dial("tcp", ip:port, redis.DialPassword(redisPasswd), redis.DialDatabase(1))
if cerr != nil {
    fmt.Println(cerr)
}
defer cli.Close()
_, err := cli.Do("SET", key, value)
if err != nil {
    fmt.Println(err)
}
阅读全文