MST

星途 面试题库

面试题:Go中反射在对象序列化场景的应用

在Go语言中,我们常常需要将结构体对象序列化为JSON或者其他格式。请简述如何利用反射来实现一个通用的结构体到JSON格式字符串的转换函数,并且说明在这个过程中反射是如何工作的,比如如何获取结构体字段信息、如何处理不同类型的字段等。
49.1万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

实现通用结构体到JSON格式字符串转换函数

  1. 初始化反射值:通过reflect.ValueOf获取结构体实例的反射值对象,使用reflect.TypeOf获取结构体类型对象。
  2. 遍历结构体字段:通过Type.NumField获取结构体字段数量,使用Type.Field(i)获取每个字段的StructField信息,Value.Field(i)获取对应字段的值。
  3. 处理不同类型字段
    • 基本类型:直接获取值并转换为JSON格式字符串。
    • 指针类型:先判断是否为nil,非nil则解引用获取实际值。
    • 切片、数组:递归处理每个元素。
    • 嵌套结构体:递归调用转换函数。
  4. 构建JSON字符串:根据JSON格式要求,拼接字段名、冒号、字段值,并处理好逗号分隔及大括号包围。

反射工作原理

  1. 获取结构体字段信息
    • reflect.TypeField方法能获取结构体中每个字段的详细信息,如名字、类型、标签等。标签信息可用于自定义JSON序列化时的字段名等。
    • reflect.ValueField方法获取对应字段的实际值。
  2. 处理不同类型字段
    • 基本类型reflect.Value有对应方法获取基本类型的值,如Int获取int值,Float获取float64值等,获取值后按JSON格式要求转换。
    • 指针类型reflect.ValueIsNil方法判断指针是否为空,Elem方法获取指针指向的值。
    • 切片、数组reflect.ValueLen获取长度,Index获取每个元素值,递归处理每个元素。
    • 嵌套结构体:递归调用转换函数,对嵌套结构体进行同样的反射处理。

以下是示例代码:

package main

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

func structToJSON(obj interface{}) (string, error) {
    val := reflect.ValueOf(obj)
    if val.Kind() == reflect.Ptr {
        val = val.Elem()
    }
    if val.Kind() != reflect.Struct {
        return "", fmt.Errorf("input is not a struct")
    }

    typeOf := val.Type()
    jsonStr := "{"
    for i := 0; i < val.NumField(); i++ {
        field := typeOf.Field(i)
        fieldVal := val.Field(i)
        jsonTag := field.Tag.Get("json")
        if jsonTag == "" {
            jsonTag = field.Name
        }
        jsonStr += fmt.Sprintf("\"%s\":", jsonTag)
        switch fieldVal.Kind() {
        case reflect.String:
            jsonStr += fmt.Sprintf("\"%s\"", fieldVal.String())
        case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
            jsonStr += fmt.Sprintf("%d", fieldVal.Int())
        case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
            jsonStr += fmt.Sprintf("%d", fieldVal.Uint())
        case reflect.Float32, reflect.Float64:
            jsonStr += fmt.Sprintf("%f", fieldVal.Float())
        case reflect.Bool:
            jsonStr += fmt.Sprintf("%t", fieldVal.Bool())
        case reflect.Struct:
            subJSON, err := structToJSON(fieldVal.Interface())
            if err != nil {
                return "", err
            }
            jsonStr += subJSON
        case reflect.Ptr:
            if fieldVal.IsNil() {
                jsonStr += "null"
            } else {
                subJSON, err := structToJSON(fieldVal.Elem().Interface())
                if err != nil {
                    return "", err
                }
                jsonStr += subJSON
            }
        case reflect.Slice, reflect.Array:
            jsonStr += "["
            for j := 0; j < fieldVal.Len(); j++ {
                elem := fieldVal.Index(j)
                subJSON, err := structToJSON(elem.Interface())
                if err != nil {
                    return "", err
                }
                jsonStr += subJSON
                if j < fieldVal.Len()-1 {
                    jsonStr += ","
                }
            }
            jsonStr += "]"
        default:
            return "", fmt.Errorf("unsupported type %v", fieldVal.Kind())
        }
        if i < val.NumField()-1 {
            jsonStr += ","
        }
    }
    jsonStr += "}"
    return jsonStr, nil
}

使用示例:

type Person struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

func main() {
    p := Person{Name: "John", Age: 30}
    jsonStr, err := structToJSON(p)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(jsonStr)

    var p2 Person
    err = json.Unmarshal([]byte(jsonStr), &p2)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(p2)
}