Reducir problema 487
¿Cómo escanear una estructura con campos anidados?#487
Para comprender mejor este artículo, se recomienda leer primero el número original.
1. ¿Cuál es el problema?
Los HGETALL
datos devueltos por el comando se analizan en la estructura correspondiente UserInfo
, pero *LiteUser
los datos de los campos de la estructura no se pueden analizar correctamente.
Si lo *LiteUser
cambias LiteUser
a .
2. Reproducir el problema
- Copie el código en el problema, instale redigo en el módulo go y prepare un servicio redis.
- La versión en redigo en go.mod se establece en la versión anterior al problema sin modificar: v1.8.1.
- Ejecutar el código reproducirá el problema de este problema y
*LiteUser
los datos del campo no se pueden analizar correctamente.
Pruebe la última versión del código y ejecútelo:
- La versión en redigo en go.mod está configurada en la última versión del problema: v1.8.8.
- Ejecutando el código, el problema no aparece.
Tenga en cuenta que para reproducir el problema en la última versión, debe agregar el siguiente código a continuación sobre la línea 73 del código de muestra (volveremos a este problema más adelante):
...
var newUser UserInfo
newUser.LiteUser = &LiteUser{}
...
复制代码
En tercer lugar, cómo resolver
Para más detalles, consulte pr 490
Antes de ver cómo resolverlo, resolvamos el proceso de ejecución:
3.1 Análisis de datos en variables de estructura
Cuando la ejecución HGETALL
obtiene los datos de Redis, necesita analizar los datos en las variables miembro de la estructura, al igual que tomar los datos de MySQL y analizarlos en las variables miembro de la estructura es un significado.
Redigo proporciona un método para pasar datos y variables de estructura, y los datos se analizarán en la newUser
estructura:
redis.ScanStruct(v, &newUser)
复制代码
3.2 Estructura de escaneo
A continuación, eche un vistazo redis.ScanStruct()
a lo que se ha hecho.
Peiné y resumí los métodos llamados en el proceso:
// 将数据解析到structSpecForType返回的结构体成员上
func ScanStruct(src []interface{}, dest interface{}) error {
//获取变量指针
d := reflect.ValueOf(dest)
//获取指针指向的变量
d = d.Elem()
structSpecForType(d.Type())
...
}
// 根据传入的reflect.Type,先去缓存中查找是否解析过,如果没有调用compileStructSpec
func structSpecForType(t reflect.Type) *structSpec {
...
compileStructSpec(t, make(map[string]int), nil, ss)
...
}
复制代码
3.3 especificación de estructura de compilación
compileStructSpec
El método implementa la resolución de tipos y el problema está realmente aquí.
Primero publique el resumen resumido:
- Use la reflexión para analizar datos
&newUser 结构体
en todas las variables miembro de - En V1.8.1 y versiones anteriores, solo se analizaba reflect.Struct (LiteUser) y no se procesaba reflect.Ptr (*LiteUser).
- En V1.8.2 y posteriores, se agrega el juicio de reflect.Ptr
A continuación se muestra la lógica central
Antes de la reparación:
func compileStructSpec(t reflect.Type, depth map[string]int, index []int, ss *structSpec) {
// t.NumField()获取结构体类型的所有字段的个数
for i := 0; i < t.NumField(); i++ {
// t.Field()返回指定的字段,类型为 StructField
f := t.Field(i)
switch {
// f.PkgPath 包路径不为空 且 不是匿名函数
// f.Anonymous 表示该字段是否为匿名字段
case f.PkgPath != "" && !f.Anonymous:
// 忽略未导出的:结构体中的某个成员改为小写(私有),就会进到这个case
// Ignore unexported fields.
// UserInfo中的成员LiteUser,并未设置 name,为匿名字段,就会进到这个case
case f.Anonymous:
// f.Type.Kind() 获取种类
// 如果当前type为结构体,进行递归调用,以处理当前type内所有结构体成员
// 对于 `LiteUser` 会进到这个 case
if f.Type.Kind() == reflect.Struct {
compileStructSpec(f.Type, depth, append(index, i), ss)
}
复制代码
Después de arreglar:
...
func compileStructSpec(t reflect.Type, depth map[string]int, index []int, ss *structSpec) {
LOOP:
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
switch {
case f.PkgPath != "" && !f.Anonymous:
// Ignore unexported fields.
case f.Anonymous:
switch f.Type.Kind() {
case reflect.Struct:
compileStructSpec(f.Type, depth, append(index, i), ss)
// 这里是变动的部分,对于 `*LiteUser` 会进到这个 case
case reflect.Ptr:
// 如果当前字段的type的值为结构体,进行递归调用,以处理当前字段内所有结构体成员
// f.Type.Kind()返回的是前f的种类,也就是reflect.Ptr
// f.Type.Elem().Kind() 返回的是前f的值的种类,也就是reflect.Struct
// TODO(steve): Protect against infinite recursion.
if f.Type.Elem().Kind() == reflect.Struct {
compileStructSpec(f.Type.Elem(), depth, append(index, i), ss)
}
}
...
复制代码
Bien~, ¡problema resuelto!
4. Expansión
4.1 Reflexión
compileStructSpec
Dentro del método, se implementa principalmente a través de la reflexión.
El punto clave aquí es hablar sobre por qué d := reflect.ValueOf(dest)
todavía se usa después de que está terminado d = d.Elem()
, citando una oración de "Ir al diseño e implementación del lenguaje".
Dado que todas las llamadas a funciones en el lenguaje Go se pasan por valor, solo podemos cambiar la variable original de forma indirecta: primero obtenga el puntero correspondiente
reflect.Value
y luego use elreflect.Value.Elem
método para obtener la variable que se puede establecer.
Referirse a
El lenguaje Go involucra e implementa - reflexión
4.2 nuevoUsuario.LiteUser = &LiteUser{}
Si int, string, etc. son tipos de valor, incluso si no se inicializan, sino que solo se declaran, el valor predeterminado será el valor "cero" de este tipo.
Pero las variables de referencia como Map, Slice, Channel, etc. deben usarse antes make()
.
Para una variable del mismo &
tipo, su valor almacena una dirección de memoria, por lo que primero debe inicializar una LiteUser
estructura y luego asignarle su dirección de memoria newUser.LiteUser
antes de que pueda usarse normalmente.
Número público: IssueHub