Go語言實現簡單的反序列化

ffff5發表於2024-09-12

這段程式碼實現了一個簡單的 JSON 序列化和反序列化功能。程式碼包括一個 User 結構體和兩個函式 MarshalUnMarshal,分別用於將資料轉換為 JSON 格式和將 JSON 格式的資料解析回結構體。

package main

import (
	"bytes"
	"fmt"
	"reflect"
	"strconv"
	"strings"
)

// User 定義了一個使用者結構體,其中包含名稱、年齡和性別欄位
type User struct {
	Name string
	Age  string
	Sex  byte `json:"gender"` // 使用 json 標籤自定義欄位名
}

// Marshal 將任意型別 v 序列化為 JSON 格式的位元組陣列
func Marshal(v interface{}) ([]byte, error) {
	value := reflect.ValueOf(v) // 獲取值的反射物件
	typ := value.Type() // 獲取值的型別
	bf := bytes.Buffer{} // 使用 bytes.Buffer 以高效地構造 JSON 字串

	switch typ.Kind() {
	case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int8, reflect.Int64, reflect.Uint8:
		// 對於整數型別,將其轉換為字串並返回位元組陣列
		return []byte(fmt.Sprintf("%v", value.Interface())), nil
	case reflect.String:
		// 對於字串型別,將其加上雙引號後返回位元組陣列
		return []byte("\"" + value.String() + "\""), nil
	case reflect.Bool:
		// 對於布林值,將其轉換為 "true" 或 "false" 字串
		return []byte(fmt.Sprintf("%t", value.Bool())), nil
	case reflect.Float32, reflect.Float64:
		// 對於浮點數型別,將其轉換為字串並返回位元組陣列
		return []byte(fmt.Sprintf("%f", value.Float())), nil
	case reflect.Struct:
		// 對於結構體型別,構造 JSON 格式的字串
		bf.WriteByte('{')
		if value.NumField() > 0 {
			for i := 0; i < value.NumField(); i++ {
				fieldValue := value.Field(i) // 獲取欄位值
				fieldType := typ.Field(i) // 獲取欄位型別
				if fieldType.IsExported() {
					// 處理匯出欄位
					name := fieldType.Name
					if len(fieldType.Tag.Get("json")) > 0 {
						// 使用 JSON 標籤指定的欄位名
						name = fieldType.Tag.Get("json")
					}
					bf.WriteByte('"')
					bf.WriteString(name)
					bf.WriteByte('"')
					bf.WriteByte(':')
					if bs, err := Marshal(fieldValue.Interface()); err == nil {
						bf.Write(bs)
					} else {
						return nil, err
					}
					bf.WriteByte(',')
				}
			}
			bf.Truncate(len(bf.Bytes()) - 1) // 刪除最後一個逗號
		}
		bf.WriteByte('}')
		return bf.Bytes(), nil
	default:
		// 對於不支援的型別,返回錯誤
		return nil, fmt.Errorf("暫不支援該型別%s", typ.Kind())
	}
}

// UnMarshal 將 JSON 格式的位元組陣列 data 解析為型別為 v 的值
func UnMarshal(v interface{}, data []byte) error {
	value := reflect.ValueOf(v) // 獲取值的反射物件
	typ := value.Type() // 獲取值的型別

	if typ.Kind() != reflect.Ptr {
		return fmt.Errorf("必須為指標")
	} else {
		typ = typ.Elem() // 獲取指標指向的型別
		value = value.Elem() // 獲取指標指向的值
	}

	s := string(data) // 將位元組陣列轉換為字串
	switch typ.Kind() {
	case reflect.Int:
		// 解析整數
		if i, err := strconv.ParseInt(s, 10, 64); err == nil {
			value.SetInt(i) // 設定解析後的整數值
		} else {
			return err
		}
	case reflect.Uint8:
		// 解析無符號整數(位元組)
		if i, err := strconv.ParseUint(s, 10, 64); err == nil {
			value.SetUint(i) // 設定解析後的位元組值
		} else {
			return err
		}
	case reflect.Bool:
		// 解析布林值
		if b, err := strconv.ParseBool(s); err == nil {
			value.SetBool(b) // 設定解析後的布林值
		} else {
			return err
		}
	case reflect.Float32:
		// 解析浮點數
		if f, err := strconv.ParseFloat(s, 64); err == nil {
			value.SetFloat(f) // 設定解析後的浮點數值
		} else {
			return err
		}
	case reflect.String:
		// 解析字串,確保字串被雙引號包圍
		if s[0] == '"' && s[len(s)-1] == '"' {
			value.SetString(s[1 : len(s)-1]) // 去掉雙引號
		} else {
			return fmt.Errorf("json格式不對")
		}
	case reflect.Struct:
		// 解析結構體
		if s[0] == '{' && s[len(s)-1] == '}' {
			arr := strings.Split(s[1:len(s)-1], ",") // 去掉大括號並拆分欄位
			if len(arr) > 0 {
				fieldCount := typ.NumField()
				tag2Field := make(map[string]string, fieldCount)
				for i := 0; i < fieldCount; i++ {
					fieldType := typ.Field(i)
					name := fieldType.Name
					if len(fieldType.Tag.Get("json")) > 0 {
						// 使用 JSON 標籤指定的欄位名
						name = fieldType.Tag.Get("json")
					}
					tag2Field[name] = fieldType.Name
				}
				for _, ele := range arr {
					brr := strings.SplitN(ele, ":", 2) // 拆分標籤和資料
					tag := brr[0]
					if tag[0] == '"' && tag[len(tag)-1] == '"' {
						tag = tag[1 : len(tag)-1]
						if fieldName, exists := tag2Field[tag]; exists {
							fieldValue := value.FieldByName(fieldName)
							fieldType := fieldValue.Type()
							if fieldValue.Kind() != reflect.Ptr {
								fieldValue = fieldValue.Addr()
								if err := UnMarshal(fieldValue.Interface(), []byte(brr[1])); err != nil {
									return err
								}
							} else {
								newValue := reflect.New(fieldType.Elem())
								if err := UnMarshal(newValue.Interface(), []byte(brr[1])); err != nil {
									return err
								} else {
									fieldValue.Set(newValue)
								}
							}
						}
					} else {
						return fmt.Errorf("json格式不對%s", tag)
					}
				}
			}
		} else {
			return fmt.Errorf("格式錯誤")
		}
	default:
		// 對於不支援的型別,返回錯誤
		return fmt.Errorf("暫不支援該資料型別%s", typ.Kind())
	}
	return nil
}

// main 函式演示瞭如何使用 Marshal 和 UnMarshal 函式
func main() {
	user := User{
		Name: "ffff5",
		Age:  "35",
		Sex:  1,
	}
	if date, err := Marshal(user); err == nil {
		fmt.Println(string(date)) // 列印序列化後的 JSON 字串

		var u2 = user
		if err := UnMarshal(&u2, date); err == nil {
			fmt.Println(u2.Name, u2.Age, u2.Sex) // 列印反序列化後的資料
		} else {
			fmt.Println(err) // 列印錯誤資訊
		}
	} else {
		fmt.Println(err) // 列印序列化錯誤
	}
}
  • User 結構體:

    • 定義了一個包含 Name(名稱)、Age(年齡)和 Sex(性別)欄位的結構體。
    • Sex 欄位使用了 json 標籤來指定 JSON 中的欄位名為 "gender"
  • Marshal 函式:

    • 將傳入的任意型別的值轉換為 JSON 格式的位元組陣列。
    • 支援整數、字串、布林值、浮點數和結構體。
    • 對於結構體,處理欄位標籤和序列化欄位值。
  • UnMarshal 函式:

    • 將 JSON 格式的位元組陣列解析為傳入指標型別 v 所指向的值。
    • 支援整數、位元組(無符號 8 位整數)、布林值、浮點數、字串和結構體。
    • 處理 JSON 格式,支援透過標籤來解析結構體欄位。
  • main 函式:

    • 建立一個 User 物件,並將其序列化為 JSON。
    • 將序列化後的 JSON 字串反序列化為新的 User 物件,並列印結果。

相關文章