Golang struct,map,json 之间的转换

Go 小记 2019-12-24 6925 字 1371 浏览 点赞

起步

利用 Go 写一个项目时,比如常见的 web server,很容易涉及到 struct,map,json 三者之间的转换。这里想简单总结下,帮助一些刚入坑的朋友。

struct <=> json

不论是 struct => json 还是 json => struct 都尤为简单,这是因为标准库 encoding/json 提供了友好的 API。

示例代码如下:

// struct_json_test.go
package main

import (
    "encoding/json"
    "log"
    "reflect"
    "testing"
)

// StructToJSON ...
func StructToJSON(o interface{}) string {
    // 确保 o 是结构体(没有必要时,校验代码可以删除)
    if reflect.TypeOf(o).Kind() != reflect.Struct {
        log.Fatal("need struct")
    }

    // []byte, error
    strJSON, _ := json.Marshal(o)
    // []byte => string
    return string(strJSON)
}

// JSONToStruct ...
func JSONToStruct(s string, targetPtr interface{}) {
    // string => []byte
    sBytes := []byte(s)
    // 填充目标结构体
    json.Unmarshal(sBytes, targetPtr)
}

// TestCurCode ...
func TestCurCode(t *testing.T) {
    // 定义一个 Student 结构体
    type Student struct {
        Name string
        Age  int
    }

    stu := Student{"zty", 18}
    strJSON := StructToJSON(stu)
    t.Logf("type: %T\n", strJSON)
    t.Logf("value: %s\n", strJSON)

    // 定义一个 JSON 字符串
    var JSONStr = `
    {
        "Name": "zty",
        "Age": 18
    }
    `
    target := Student{}
    JSONToStruct(JSONStr, &target)
    t.Logf("type: %T\n", target)
    t.Logf("value: %v\n", target)
}

struct 与 json 之间的转换主要靠两个 API:

  • json.Marshal
  • json.Unmarshal

需要注意 json.Unmarshal 的第二个参数是目标结构体的地址。

map <=> json

// struct_json_test.go
package main

import (
    "encoding/json"
    "testing"
)

// MapToJSON ...
func MapToJSON(m map[string]interface{}) string {
    strJSON, _ := json.Marshal(m)
    // []byte => string
    return string(strJSON)
}

// JSONToMap ...
func JSONToMap(s string) map[string]interface{} {
    // 初始化一个 map
    tarMap := make(map[string]interface{})
    // string => []byte
    sBytes := []byte(s)
    // 填充 map
    json.Unmarshal(sBytes, &tarMap)
    return tarMap
}

func TestCurCode(t *testing.T) {
    srcMap := map[string]interface{}{
        "Name": "zty",
        "Age":  18,
    }
    strJSON := MapToJSON(srcMap)
    t.Logf("type: %T", strJSON)
    t.Logf("value: %v", strJSON)

    var JSONStr = `
    {
        "Name": "zty",
        "Age":  18
    }
    `
    m := JSONToMap(JSONStr)
    t.Logf("type: %T", m)
    t.Logf("value: %v", m)
}

map 和 json 之间的转换与 struct 和 json 之间的转换同出一辙,调用的 API 也是一样的,就不再多口舌了。

struct <=> map

在有了上面的知识储备之后,我们可以很快想到 struct、json 转换的第一种方法

  • struct => json => map
  • map => json => struct

即:以 json 作为媒介,为 struct 和 map 牵线。

第二种方法,利用官方提供的 reflect 库。

// struct_map_test.go
package main

import (
    "log"
    "reflect"
    "testing"
)

// StructToMap ...
func StructToMap(o interface{}) map[string]interface{} {
    t := reflect.TypeOf(o)
    v := reflect.ValueOf(o)
    // 结构体类型校验
    if t.Kind() != reflect.Struct {
        log.Fatal("need struct")
    }

    tarMap := make(map[string]interface{})
    for i := 0; i < t.NumField(); i++ {
        // t.Field(i).Name    返回 field 的名字
        // v.Field(i).Interface() 返回 field 对应值的 interface{} 类型
        tarMap[t.Field(i).Name] = v.Field(i).Interface()
    }
    return tarMap
}

func MapToStruct(m map[string]interface{}, target interface{}) {
    // 确保传递进来的是指针类型
    if reflect.TypeOf(target).Kind() == reflect.Ptr {
        // 确保指针指向的是结构体
        if reflect.TypeOf(target).Elem().Kind() == reflect.Struct {
            var (
                field reflect.StructField
                ok    bool
            )
            for k, v := range m {
                // 根据 map 键名,找到结构体中对应的字段域
                field, ok = reflect.TypeOf(target).Elem().FieldByName(k)
                // 确保字段类型与 map 中的元素类型一致
                if ok && field.Type == reflect.TypeOf(v) {
                    // 找到对应字段,赋值
                    reflect.ValueOf(target).Elem().FieldByName(k).Set(
                        reflect.ValueOf(v),
                    )
                }
            }
        }
    }
}

func TestCurCode(t *testing.T) {
    // 定义一个 Student 结构体
    type Student struct {
        Name string
        Age  int
    }
    m := StructToMap(Student{"zty", 18})
    t.Logf("type: %T", m)
    t.Logf("value: %v", m)

    srcMap := map[string]interface{}{
        "Name": "zty",
        "Age":  18,
    }
    target := Student{}
    MapToStruct(srcMap, &target)
    t.Logf("type: %T", target)
    t.Logf("value: %v", target)
}

这里需要简单说明一下上述代码中用到的 reflect 库的 API。

  • reflect.TypeOf()reflect.ValueOf()

前者用来获取变量的static type,后者用来获取变量对应的值。当然,得到的内容已经被封装在结构体中了。

var zty string = "Hello World"
t := reflect.TypeOf(zty)
fmt.Printf("TypeOf: type(%T), value(%v)\n", t, t)

v := reflect.ValueOf(zty)
fmt.Printf("ValueOf: type(%T), value(%v)\n", v, v)

// 输出:
TypeOf: type(*reflect.rtype), value(string)
TypeOf: type(reflect.Value), value(Hello World)

由于这些结构体实现了 String()方法,通过打印结构体对象,可以获取到它们存储的核心数据。前者是 string,后者是Hello World。印证了前面的话,reflect.TypeOf() 存储变量类型,reflect.ValueOf() 存储变量内容。

  • reflect.TypeOf(v).Kind()

reflect.TypeOf() 方法只是返回了存放类型的结构体,而调用这个结构体的 Kind() 方法能够获取到这个类型的类别。类别表示“哪一类”的意思,如 *int, *string 都属于指针类型;type x struct {...}type y struct {...} x、y 都属于结构体类型。在 reflect 库中提供了 reflect.Struct、reflect.Ptr 等枚举来与 Kind() 的返回值判等。

  • reflect.TypeOf(v).Field()reflect.TypeOf(v).FieldName()reflect.ValueOf(v).Field()reflect.ValueOf(v).FieldName()

以上接口 reflect.TypeOf 都是用来获取结构体中的字段名、字段类型等属性;reflect.ValueOf 用来获取字段对应的值。Field(i int) 通过索引值拿到字段/值,FieldName(name string) 通过字段名拿到字段/值。

type Student struct {
    Name string
}

stu := Student{"zty"}
// TypeOf
fieldByFieldTypeof := reflect.TypeOf(stu).Field(0)
fmt.Printf("TypeOf Field: type(%T), value(%v)\n", fieldByFieldTypeof, fieldByFieldTypeof)
fieldByFieldNameTypeof, _ := reflect.TypeOf(stu).FieldByName("Name")
fmt.Printf("TypeOf FieldName: type(%T), value(%v)\n", fieldByFieldNameTypeof, fieldByFieldNameTypeof)
// ValueOf
fieldByFieldValueof := reflect.ValueOf(stu).Field(0)
fmt.Printf("ValueOf Field: type(%T), value(%v)\n", fieldByFieldValueof, fieldByFieldValueof)
fieldByFieldNameValueof := reflect.ValueOf(stu).FieldByName("Name")
fmt.Printf("ValueOf FieldName: type(%T), value(%v)\n", fieldByFieldNameValueof, fieldByFieldNameValueof)

// 输出:
TypeOf Field: type(reflect.StructField), value({Name  string  0 [0] false})
TypeOf FieldName: type(reflect.StructField), value({Name  string  0 [0] false})

ValueOf Field: type(reflect.Value), value(zty)
ValueOf FieldName: type(reflect.Value), value(zty)

注意:

reflect.TypeOf(stu).FieldByName("Name") 与 reflect.ValueOf(stu).FieldByName("Name") 的返回值个数不同。前者还会返回一个 bool 值,用来说明该字段是否存在;后者只有一个返回值,当这个字段不存在时,返回 refelct.Invalid 类型,打印出来的效果是:

  • reflect.TypeOf(v).Elem()reflect.ValueOf(v).Elem()

当传入的 v 是一根指针时,就可以调用 Elem() 方法,表示获取指针指向的结构体,然后仍然是层层封装,最后得到的类型分别是 *reflect.rtype、reflect.Value。也就是说,当 v 是指针的时候,reflect.TypeOf(v).Elem() 等同于 reflect.TypeOf(*v)reflect.ValueOf(v).Elem() 等同于 reflect.ValueOf(*v)

  • reflect.ValueOf(v).Elem().FieldByName("k").Set()

为结构体中的字段赋值。Set() 接收的是 reflect.Value 类型,因此传入的值需要先经过 reflect.VaueOf() 做类型转换。

感谢



本文由 Guan 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

还不快抢沙发

添加新评论