Golang自定義結構體轉map 第二個人的思路
以下內容轉載自 https://juejin.cn/post/6855129007193915400
在Golang中,如何將一個結構體轉成map? 本文介紹兩種方法。第一種是是使用json
包解析解碼編碼。第二種是使用反射,使用反射的效率比較高,程式碼在這裡。如果覺得程式碼有用,可以給我的程式碼倉庫一個star。
假設有下面的一個結構體
func newUser() User {
name := "user"
MyGithub := GithubPage{
URL: "https://github.com/liangyaopei",
Star: 1,
}
NoDive := StructNoDive{NoDive: 1}
dateStr := "2020-07-21 12:00:00"
date, _ := time.Parse(timeLayout, dateStr)
profile := Profile{
Experience: "my experience",
Date: date,
}
return User{
Name: name,
Github: MyGithub,
NoDive: NoDive,
MyProfile: profile,
}
}
type User struct {
Name string `map:"name,omitempty"` // string
Github GithubPage `map:"github,dive,omitempty"` // struct dive
NoDive StructNoDive `map:"no_dive,omitempty"` // no dive struct
MyProfile Profile `map:"my_profile,omitempty"` // struct implements its own method
}
type GithubPage struct {
URL string `map:"url"`
Star int `map:"star"`
}
type StructNoDive struct {
NoDive int
}
type Profile struct {
Experience string `map:"experience"`
Date time.Time `map:"time"`
}
// its own toMap method
func (p Profile) StructToMap() (key string, value interface{}) {
return "time", p.Date.Format(timeLayout)
}
複製程式碼
json包的marshal,unmarshal
先將結構體序列化成[]byte
陣列,再從[]byte
陣列序列化成結構體。
data, _ := json.Marshal(&user)
m := make(map[string]interface{})
json.Unmarshal(data, &m)
複製程式碼
優勢
- 使用簡單 劣勢
- 效率比較慢
- 不能支援一些定製的鍵,也不能支援一些定製的方法,例如將struct的域展開等。
使用反射
本文實現了使用反射將結構體轉成map
的方法。通過標籤(tag)和反射,將上文示例的newUser()
返回的結果轉化成下面的一個map
。其中包含struct的域的展開,定製化struct的方法。
map[string]interface{}{
"name": "user",
"no_dive": StructNoDive{NoDive: 1},
// dive struct field
"url": "https://github.com/liangyaopei",
"star": 1,
// customized method
"time": "2020-07-21 12:00:00",
}
複製程式碼
實現思路 & 原始碼解析
1.標籤識別。
使用readTag
方法讀取域(field)的標籤,如果沒有標籤,使用域的名字。然後讀取tag中的選項。目前支援3個選項
- '-':忽略當前這個域
- 'omitempty' : 當這個域的值為空,忽略這個域
- 'dive' : 遞迴地遍歷這個結構體,將所有欄位作為鍵
如果選中了一個選項,就講這個域對應的二進位制位置為1.。
const (
OptIgnore = "-"
OptOmitempty = "omitempty"
OptDive = "dive"
)
const (
flagIgnore = 1 << iota
flagOmiEmpty
flagDive
)
func readTag(f reflect.StructField, tag string) (string, int) {
val, ok := f.Tag.Lookup(tag)
fieldTag := ""
flag := 0
// no tag, use field name
if !ok {
return f.Name, flag
}
opts := strings.Split(val, ",")
fieldTag = opts[0]
for i := 1; i < len(opts); i++ {
switch opts[i] {
case OptIgnore:
flag |= flagIgnore
case OptOmitempty:
flag |= flagOmiEmpty
case OptDive:
flag |= flagDive
}
}
return fieldTag, flag
}
複製程式碼
2.結構體的域(field)的遍歷。
遍歷結構體的每一個域(field),判斷field的型別(kind)。如果是string
,int
等的基本型別,直接取值,並且把標籤中的值作為key。
for i := 0; i < t.NumField(); i++ {
...
switch fieldValue.Kind() {
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int, reflect.Int64:
res[tagVal] = fieldValue.Int()
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint, reflect.Uint64:
res[tagVal] = fieldValue.Uint()
case reflect.Float32, reflect.Float64:
res[tagVal] = fieldValue.Float()
case reflect.String:
res[tagVal] = fieldValue.String()
case reflect.Bool:
res[tagVal] = fieldValue.Bool()
default:
}
}
}
複製程式碼
3.內嵌結構體的轉換
如果是結構體,先檢查有沒有實現傳入引數的方法,如果實現了,就呼叫這個方法。如果沒有實現,就遞迴地呼叫StructToMap
方法,然後根據是否展開(dive
),來把返回結果寫入res的map。
for i := 0; i < t.NumField(); i++ {
fieldType := t.Field(i)
// ignore unexported field
if fieldType.PkgPath != "" {
continue
}
// read tag
tagVal, flag := readTag(fieldType, tag)
if flag&flagIgnore != 0 {
continue
}
fieldValue := v.Field(i)
if flag&flagOmiEmpty != 0 && fieldValue.IsZero() {
continue
}
// ignore nil pointer in field
if fieldValue.Kind() == reflect.Ptr && fieldValue.IsNil() {
continue
}
if fieldValue.Kind() == reflect.Ptr {
fieldValue = fieldValue.Elem()
}
// get kind
switch fieldValue.Kind() {
case reflect.Struct:
_, ok := fieldValue.Type().MethodByName(methodName)
if ok {
key, value, err := callFunc(fieldValue, methodName)
if err != nil {
return nil, err
}
res[key] = value
continue
}
// recursive
deepRes, deepErr := StructToMap(fieldValue.Interface(), tag, methodName)
if deepErr != nil {
return nil, deepErr
}
if flag&flagDive != 0 {
for k, v := range deepRes {
res[k] = v
}
} else {
res[tagVal] = deepRes
}
default:
}
}
...
}
// call function
func callFunc(fv reflect.Value, methodName string) (string, interface{}, error) {
methodRes := fv.MethodByName(methodName).Call([]reflect.Value{})
if len(methodRes) != methodResNum {
return "", nil, fmt.Errorf("wrong method %s, should have 2 output: (string,interface{})", methodName)
}
if methodRes[0].Kind() != reflect.String {
return "", nil, fmt.Errorf("wrong method %s, first output should be string", methodName)
}
key := methodRes[0].String()
return key, methodRes[1], nil
}
複製程式碼
4.array,slice型別的轉換
如果是array
,slice
型別,類似地,檢查有沒有實現傳入引數的方法,如果實現了,就呼叫這個方法。如果沒有實現,將這個field的tag作為key,域的值作為value。
switch fieldValue.Kind() {
case reflect.Slice, reflect.Array:
_, ok := fieldValue.Type().MethodByName(methodName)
if ok {
key, value, err := callFunc(fieldValue, methodName)
if err != nil {
return nil, err
}
res[key] = value
continue
}
res[tagVal] = fieldValue
....
}
複製程式碼
5.其他型別
對於其他型別,例如內嵌的map
,直接將其返回結果的值。
switch fieldValue.Kind() {
...
case reflect.Map:
res[tagVal] = fieldValue
case reflect.Chan:
res[tagVal] = fieldValue
case reflect.Interface:
res[tagVal] = fieldValue.Interface()
default:
}
複製程式碼
關注下面的標籤,發現更多相似文章
作者:liangyaopei_
連結:https://juejin.cn/post/6855129007193915400
來源:掘金
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。
相關文章
- Golang操作結構體、Map轉化為JSONGolang結構體JSON
- golang map的底層結構Golang
- golang json字串轉結構體GolangJSON字串結構體
- 理解 Golang 的 map 資料結構設計Golang資料結構
- golang 結構體自定義排序 + 按照分數算排名同分數排名一樣Golang結構體排序
- 個人技術棧大體思路總結
- 一個 key 能儲存多個 value 的 map --- 自定義的 MultiValueMap,實現 Map 介面
- golang類和結構體Golang結構體
- sqlite中存放自定義表結構的位置SQLite
- 自定義響應資料結構資料結構
- golang sync.Map之如何設計一個併發安全的讀寫分離結構?Golang
- 自定義限速功能實踐——Map 版本
- golang中struct、json、map互相轉化GolangStructJSON
- Angular 自定義結構型指令 structural directive 的使用AngularStruct
- [譯] Part 31: Golang 中的自定義ErrorGolangError
- 認真一點學 Go:12. 自定義型別和結構體 - 定義Go型別結構體
- [譯] part 16: golang 結構體structuresGolang結構體Struct
- Golang 學習——結構體 struct (一)Golang結構體Struct
- Golang 學習——結構體 struct (二)Golang結構體Struct
- 10 分鐘搞定 Golang 結構體Golang結構體
- 分享一個無需定義結構體解析json的包結構體JSON
- Golang map 原始碼解析和結構圖解 https://www.weiboke.onlineGolang原始碼圖解HTTP
- 踩了 Golang sync.Map 的一個坑Golang
- 如何定義一個自帶資料區的結構體:三種資料結構體的比較結構體資料結構
- Map 資料結構資料結構
- Angular 自定義結構化指令,如何傳入多個輸入引數Angular
- golang 學習之路之 struct 結構體GolangStruct結構體
- oracle體系結構(轉)Oracle
- 理解Golang的Time結構Golang
- 如何構建自定義人臉識別資料集
- PHP自定義問卷調查的設計及思路PHP
- 認真一點學 Go:13. 自定義型別和結構體 - 方法Go型別結構體
- map自定義排序,根據鍵或者值排隊排序
- javascript實現Map結構JavaScript
- 繫結自定義事件事件
- 自定義值轉換器
- C++ - 結構體轉cha*C++結構體
- Swift 專案總結 04 自定義控制器轉場Swift