golang通過反射手動實現(xiàn)json序列化的方法
一、json
在 Go 語言中,JSON 序列化和反序列化通常通過標準庫 encoding/json
來實現(xiàn)。這個包提供了簡單易用的接口來將 Go 數(shù)據結構轉換為 JSON 格式字符串(序列化),以及從 JSON 字符串解析出 Go 數(shù)據結構(反序列化)。
1.1 序列化(將 Go 對象轉換為 JSON)
使用 json.Marshal()
函數(shù)可以將一個 Go 對象轉換為 JSON 字符串。
import ( "encoding/json" "fmt" "testing" ) type TestJson struct { UserId int `json:"user_id"` // 使用結構體標簽可以指定字段在 JSON 中的鍵名 UserNickname string // 如果沒有指定標簽,則默認使用字段名。 UserAge int `json:"age,omitempty"` // 使用omitempty可以在序列化時忽略空值 } func Test1(t *testing.T) { tJson1 := TestJson{1, "Jackson", 18} tJson2 := new(TestJson) jsonStr1, _ := json.Marshal(tJson1) jsonStr2, _ := json.Marshal(tJson2) fmt.Println(string(jsonStr1)) fmt.Println(string(jsonStr2)) }
輸出
{"user_id":1,"UserNickname":"Jackson","age":18}
{"user_id":0,"UserNickname":""}
美觀打印的序列化
如果需要生成格式良好的、可讀性更高的輸出,可以使用 json.MarshalIndent()
func Test2(t *testing.T) { tJson1 := TestJson{1, "Jackson", 18} jsonStr1, _ := json.MarshalIndent(tJson1, "", "\t") fmt.Println(string(jsonStr1)) }
輸出
{
"user_id": 1,
"UserNickname": "Jackson",
"age": 18
}
1.2 反序列化(將 JSON 轉換為 Go 對象)
使用 json.Unmarshal()
函數(shù)可以將一個 JSON 字符串解析到相應的 Go 數(shù)據結構中。
func Test3(t *testing.T) { jsonStr := `{ "user_id": 1, "UserNickname": "Jackson", "age": 18 }` var tJson TestJson // 將字符串轉為字節(jié)數(shù)組,再傳入解析后的對象指針 // 第二個參數(shù)必須是指向目標數(shù)據類型變量的指針,以便函數(shù)能夠修改該變量。 err := json.Unmarshal([]byte(jsonStr), &tJson) if err != nil { fmt.Println("解析錯誤:", err) } else { fmt.Println(tJson) } }
輸出
{1 Jackson 18}
1.3 注意
- 字段導出:只有導出的字段(即首字母大寫)才能被編碼/解碼。
- 錯誤處理:始終檢查返回錯誤,以確保數(shù)據正確處理。
- 多余的信息:解析的結構體之外的字段會被丟棄
func Test5(t *testing.T) { jsonStr := `{ "user_id": 1, "UserNickname": "Jackson", "age": 18, "addr": ["地址1","地址2"], "info":{ "id":"2", "name":"a" } }` var tJson TestJson err := json.Unmarshal([]byte(jsonStr), &tJson) if err != nil { fmt.Println("解析錯誤:", err) } else { fmt.Println(tJson) } }
輸出
{1 Jackson 18}
靈活性:對于未知或動態(tài)數(shù)據,可以考慮使用 map 或 interface{} 來接收解碼結果,但這會喪失一些類型安全特性。
func Test4(t *testing.T) { jsonStr := `{ "user_id": 1, "UserNickname": "Jackson", "age": 18, "addr": ["地址1","地址2"], "info":{ "id":"2", "name":"a" } }` var tJson map[string]any err := json.Unmarshal([]byte(jsonStr), &tJson) if err != nil { fmt.Println("解析錯誤:", err) } else { fmt.Println(tJson) } }
輸出
map[UserNickname:Jackson addr:[地址1 地址2] age:18 info:map[id:2 name:a] user_id:1]
二、反射手動實現(xiàn)
encoding/json
包在內部大量使用了反射來實現(xiàn)其功能。
JSON 序列化中的反射
- 在調用
json.Marshal()
時,Go 使用反射來檢查傳入對象的類型。 - 通過
reflect.TypeOf()
和reflect.ValueOf()
獲取類型信息和實際值。 - 遍歷結構體字段,讀取標簽(如
json:"name"
),并根據字段類型生成相應的 JSON 字符串。
JSON 反序列化中的反射
- 在調用
json.Unmarshal()
時,Go 使用目標變量指針,通過反射確定需要填充的數(shù)據結構。 - 根據 JSON 數(shù)據中的鍵名,通過標簽映射找到對應結構體字段,并設置其值。
2.1 json簡單序列化
思路:模仿 encoding/json
庫,讀取結構體標簽指定序列化的字段名
- 反射傳入的結構體
- 獲取字段
- 判斷字段的標簽
- 拼接字符串
func serializeSimple(data interface{}) string { var resultStr string = "{" //1、反射傳入的結構體 reflectDataValue := reflect.ValueOf(data) reflectDataType := reflectDataValue.Type() if reflectDataValue.Kind() == reflect.Ptr { reflectDataValue = reflectDataValue.Elem() } if reflectDataType.Kind() == reflect.Ptr { reflectDataType = reflectDataType.Elem() } //2、獲取字段 for i := 0; i < reflectDataType.NumField(); i++ { field := reflectDataType.Field(i) // 字段名 var filedName = field.Name // 字段的值 filedValue := reflectDataValue.Field(i).Interface() //3、判斷字段的標簽 if value, ok := field.Tag.Lookup("json"); ok { // 如果有json標簽,則使用其定義的命名 filedName = strings.ReplaceAll(value, ",omitempty", "") // 是否忽略空值 if strings.Contains(value, "omitempty") { if filedValue == "" || filedValue == 0 { continue } } } // 拼接json resultStr += fmt.Sprintf("\"%s\":\"%v\"", filedName, filedValue) resultStr += "," } //4、拼接字符串 resultStr += "}" // 去掉結尾的,號 return strings.ReplaceAll(resultStr, ",}", "}") } func Test6(t *testing.T) { tJson1 := TestJson{1, "Jackson", 18} tJson2 := new(TestJson) jsonStr1, _ := json.Marshal(tJson1) jsonStr2, _ := json.Marshal(tJson2) fmt.Println(string(jsonStr1)) fmt.Println(string(jsonStr2)) fmt.Println("=========自定義實現(xiàn)=========") fmt.Println(serializeSimple(tJson1)) fmt.Println(serializeSimple(tJson2)) }
輸出
{"user_id":1,"UserNickname":"Jackson","age":18}
{"user_id":0,"UserNickname":""}
=========自定義實現(xiàn)=========
{"user_id":"1","UserNickname":"Jackson","age":"18"}
{"user_id":"0","UserNickname":""}
2.2 json簡單反序列化
思路
- 切割傳入的字符串,拆分成key value的鍵值對
- 反射結構體,判斷結構體標簽后生成一個字段和值的map
- 判斷字段類型,set進對應類型的值
func parseSimple(str string, dataTemp interface{}) { // 判斷是否標準的json格式數(shù)據 if str != "" && str[0] == '{' && str[len(str)-1] == '}' { // 替換掉前后的{} str = strings.ReplaceAll(strings.ReplaceAll(str, "{", ""), "}", "") // 將結構體的標簽解析后,塞入map中備用 :map [字段名] 字段地址 structMap := map[string]reflect.Value{} // 通過反射獲取字段名 rValue := reflect.ValueOf(dataTemp) if rValue.Kind() == reflect.Ptr { rValue = rValue.Elem() } rType := rValue.Type() for i := 0; i < rType.NumField(); i++ { name := rType.Field(i).Name // 如果有定義標簽,則使用標簽的字段名 if lookup, ok := rType.Field(i).Tag.Lookup("json"); ok { name = strings.ReplaceAll(lookup, ",omitempty", "") } // 將字段名和值映射起來 structMap[name] = rValue.Field(i) } // 按照,切割每個鍵值對 splitList := strings.Split(str, ",") for i := range splitList { s := splitList[i] // 按照:切割出key 和 value keyValue := strings.Split(s, ":") key := keyValue[0] key = strings.ReplaceAll(strings.TrimSpace(key), "\"", "") // 去除前后的空格 value := keyValue[1] value = strings.ReplaceAll(strings.TrimSpace(value), "\"", "") // 按照key將value塞回結構體 switch structMap[key].Type().Kind() { // 注意判斷類型,目前只寫int和string,其他的類似 case reflect.Int: intValue, _ := strconv.Atoi(value) structMap[key].SetInt(int64(intValue)) case reflect.String: structMap[key].SetString(value) default: panic("暫不支持的數(shù)據類型") } } } } func Test7(t *testing.T) { jsonStr := `{ "user_id": 1, "UserNickname": "Jackson", "age": 18 }` var tJson1 TestJson var tJson2 TestJson err := json.Unmarshal([]byte(jsonStr), &tJson1) if err != nil { fmt.Println("解析錯誤:", err) } else { fmt.Println(tJson1) } fmt.Println("=========自定義實現(xiàn)=========") parseSimple(jsonStr, &tJson2) fmt.Println(tJson2) }
2.3 問題
- 只能處理結構體類型的數(shù)據,不支持嵌套復雜數(shù)據結構(如切片或映射)
- 在序列化的實現(xiàn)中,整數(shù)或布爾值會被轉換為帶引號的字符串形式。
- 復雜的結構體不支持,可能會出問題
所以手動實現(xiàn) JSON 序列化和反序列化只是幫助我們更好地理解數(shù)據格式與程序語言之間的映射關系,在實際開發(fā)中,使用標準庫 encoding/json
是更高效且可靠的方法,因為它已經考慮了許多復雜情況,并進行了性能優(yōu)化。
2.4 總結
2.4.1 單引號和雙引號定義的字符串有什么區(qū)別
在 json簡單反序列化的例子中,使用了單引號和雙引號定義不同類型的字符數(shù)據。具體的區(qū)別是啥呢?
單引號 ('
):
- 單引號用于表示一個字符(rune)。在 Go 中,
rune
是一個別名,代表int32
類型,用于表示 Unicode 碼點。 - 一個用單引號括起來的字符常量只能包含一個字符。例如:
'a'
,'中'
,'??'
。
雙引號 ("
):
- 雙引號用于定義字符串(string)。字符串是由一系列字節(jié)組成的數(shù)據類型,可以包含零個或多個字符。
- 字符串可以包括轉義序列,例如:
\n
,\t
,\"
等。 - 例如:“hello”, “世界”, “Go is fun\n”.
2.4.2 string如何轉為int
在 json簡單反序列化的例子中,使用了strconv.Atoi
函數(shù)將字符串(string
)轉換為整數(shù)(int
),除此之外還有strconv.ParseInt
strconv.Atoi
函數(shù)將字符串轉換為int
類型。如果轉換成功,它返回轉換后的整數(shù)和nil
錯誤;如果失敗,它返回0
和錯誤。strconv.ParseInt
函數(shù)將字符串轉換為int64
類型,并且允許你指定基數(shù)和位大小。如果你需要轉換為int
類型,你可能需要根據平臺(32位或64位)來決定如何處理結果。
func Test8(t *testing.T) { str := "123" // 第二個參數(shù)是基數(shù),10表示十進制 // 第三個參數(shù)是位大小,0表示int64,32表示int32,使用32或64取決于你的系統(tǒng)架構 num, err := strconv.ParseInt(str, 10, 0) if err != nil { fmt.Println("轉換錯誤:", err) } else { fmt.Println("轉換結果:", num) // 如果你需要int32,可以這樣轉換 num32 := int32(num) fmt.Println("轉換為int32結果:", num32) // 如果你需要int,可以這樣轉換(注意:在32位系統(tǒng)上這將是int32,在64位系統(tǒng)上這將是int64) numInt := int(num) fmt.Println("轉換為int結果:", numInt) } }
輸出:
轉換結果: 123
轉換為int32結果: 123
轉換為int結果: 123
到此這篇關于golang 通過反射手動實現(xiàn)json序列化的文章就介紹到這了,更多相關golang json序列化內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Go語言中比較兩個map[string]interface{}是否相等
本文主要介紹了Go語言中比較兩個map[string]interface{}是否相等,我們可以將其轉化成順序一樣的 slice ,然后再轉化未json,具有一定的參考價值,感興趣的可以了解一下2023-08-08golang validator庫參數(shù)校驗實用技巧干貨
這篇文章主要為大家介紹了validator庫參數(shù)校驗實用技巧干貨,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步早日升職加薪2022-04-04