Skip to content

In a nested structure, when there is an external attribute with the same name as the internal structure, gconv.Scan will be affected by the position order of the nested structure and the result will be unstable. #3800

@alaywn

Description

@alaywn

Go version

go version go1.23.1 darwin/arm64

GoFrame version

2.7.3

Can this bug be reproduced with the latest release?

Option Yes

What did you do?

如下是单测:

package test

import (
	"fmt"
	"testing"

	"github.com/gogf/gf/v2/util/gconv"
)

type NullID string

type StructA struct {
	Superior    string `json:"superior"`
	UpdatedTick int    `json:"updated_tick"`
}
type StructB struct {
	Superior    *NullID `json:"superior"`
	UpdatedTick *int    `json:"updated_tick"`
}

type StructC struct {
	Superior    string `json:"superior"`
	UpdatedTick int    `json:"updated_tick"`
}
type StructD struct {
	StructC
	Superior    *NullID `json:"superior"`
	UpdatedTick *int    `json:"updated_tick"`
}

type StructE struct {
	Superior    string `json:"superior"`
	UpdatedTick int    `json:"updated_tick"`
}
type StructF struct {
	Superior    *NullID `json:"superior"`
	UpdatedTick *int    `json:"updated_tick"`
	StructE
}

type StructG struct {
	Superior    string `json:"superior"`
	UpdatedTick int    `json:"updated_tick"`
}
type StructH struct {
	Superior    *string `json:"superior"`
	UpdatedTick *int    `json:"updated_tick"`
	StructG
}

type StructI struct {
	Master struct {
		Superior    *NullID `json:"superior"`
		UpdatedTick int     `json:"updated_tick"`
	} `json:"master"`
}
type StructJ struct {
	StructA
	Superior    *NullID `json:"superior"`
	UpdatedTick *int    `json:"updated_tick"`
}

type StructK struct {
	Master struct {
		Superior    *NullID `json:"superior"`
		UpdatedTick int     `json:"updated_tick"`
	} `json:"master"`
}
type StructL struct {
	Superior    *NullID `json:"superior"`
	UpdatedTick *int    `json:"updated_tick"`
	StructA
}

func TestScan(t *testing.T) {
	// 测试1:没有结构嵌套,Superior为带字母的string,正常
	fmt.Println("测试1")
	structA := StructA{
		Superior:    "superior100",
		UpdatedTick: 20,
	}
	structB := StructB{}
	if err := gconv.Scan(structA, &structB); err != nil {
		fmt.Printf("structA to structB: %s", err.Error())
	}
	fmt.Println("structB.Superior:", *structB.Superior)
	fmt.Println("structB.UpdatedTick:", *structB.UpdatedTick)

	// 测试2:没有结构嵌套,Superior为纯数字的string,正常
	fmt.Println("测试2")
	structA1 := StructA{
		Superior:    "100",
		UpdatedTick: 20,
	}
	structB1 := StructB{}
	if err := gconv.Scan(structA1, &structB1); err != nil {
		fmt.Printf("structA1 to structB1: %s", err.Error())
	}
	fmt.Println("structB1.Superior:", *structB1.Superior)
	fmt.Println("structB1.UpdatedTick:", *structB1.UpdatedTick)

	// 测试3:结构嵌套,Superior为带字母的string,正常
	fmt.Println("测试3")
	structC := StructC{
		Superior:    "superior100",
		UpdatedTick: 20,
	}
	structD := StructD{}
	if err := gconv.Scan(structC, &structD); err != nil {
		fmt.Printf("structC to structD: %s", err.Error())
	}
	fmt.Println("structD.StructC.Superior:", structD.StructC.Superior)
	fmt.Println("structD.Superior:", *structD.Superior)
	fmt.Println("structD.UpdatedTick:", *structD.UpdatedTick)

	// 测试4:结构嵌套,Superior为纯数字的string,出现 structD1.Superior 值为 nil
	fmt.Println("测试4")
	structC1 := StructC{
		Superior:    "100",
		UpdatedTick: 20,
	}
	structD1 := StructD{}
	if err := gconv.Scan(structC1, &structD1); err != nil {
		fmt.Printf("structC1 to structD1: %s", err.Error())
	}
	fmt.Println("structD1.StructC.Superior:", structD1.StructC.Superior) // structD1.StructC.Superior: 100
	fmt.Println("structD1.Superior:", structD1.Superior)                 // structD1.Superior: <nil>
	fmt.Println("structD1.UpdatedTick:", *structD1.UpdatedTick)          // structD1.UpdatedTick: 20

	// 测试5:结构嵌套,Superior为带字母的string,正常
	fmt.Println("测试5")
	structE := StructE{
		Superior:    "superior100",
		UpdatedTick: 20,
	}
	structF := StructF{}
	if err := gconv.Scan(structE, &structF); err != nil {
		fmt.Printf("structE to structF: %s", err.Error())
	}
	fmt.Println("structF.StructE.Superior:", structF.StructE.Superior)
	fmt.Println("structF.Superior:", *structF.Superior)
	fmt.Println("structF.UpdatedTick:", *structF.UpdatedTick)

	// 测试6:结构嵌套,Superior为纯数字的string,正常
	fmt.Println("测试6")
	structE1 := StructE{
		Superior:    "100",
		UpdatedTick: 20,
	}
	structF1 := StructF{}
	if err := gconv.Scan(structE1, &structF1); err != nil {
		fmt.Printf("structE1 to structF1: %s\n", err.Error()) // 报错
	}
	fmt.Println("structF1.StructE.Superior:", structF1.StructE.Superior) // structF1.StructE.Superior: 100
	fmt.Println("structF1.Superior:", *structF1.Superior)                // structF1.Superior: 100
	fmt.Println("structF1.UpdatedTick:", *structF1.UpdatedTick)          // structF1.UpdatedTick: 20

	// 测试7:结构嵌套,Superior为带字母的string,目标 Superior 类型 为 *string,正常
	fmt.Println("测试7")
	structG := StructG{
		Superior:    "superior100",
		UpdatedTick: 20,
	}
	structH := StructH{}
	if err := gconv.Scan(structG, &structH); err != nil {
		fmt.Printf("structG to structH: %s\n", err.Error()) // 报错
	}
	fmt.Println("structH.StructG.Superior:", structH.StructG.Superior) // structH.StructG.Superior: superior100
	fmt.Println("structH.Superior:", *structH.Superior)                // structH.Superior: superior100
	fmt.Println("structH.UpdatedTick:", *structH.UpdatedTick)          // structH.UpdatedTick: 20

	// 测试8:结构嵌套,Superior为纯数字的string,目标 Superior 类型 为 *string
	// 报错 error binding srcValue to attribute "Superior": json.UnmarshalUseNumber failed: json: cannot unmarshal number into Go value of type string
	fmt.Println("测试8")
	structG1 := StructG{
		Superior:    "100",
		UpdatedTick: 20,
	}
	structH1 := StructH{}
	if err := gconv.Scan(structG1, &structH1); err != nil {
		fmt.Printf("structG to structH: %s\n", err.Error()) // 报错
	}
	fmt.Println("structH1.StructG.Superior:", structH1.StructG.Superior) // structH1.StructG.Superior: ""
	fmt.Println("structH1.Superior:", *structH1.Superior)                // structH1.Superior: 100
	fmt.Println("structH1.UpdatedTick:", structH1.UpdatedTick)           // structH1.UpdatedTick: <nil>

	// 测试9:结构嵌套,structJ.Superior 值为 nil
	fmt.Println("测试9")
	structI := StructI{}
	xxx := NullID("superior100")
	structI.Master.Superior = &xxx
	structI.Master.UpdatedTick = 30
	structJ := StructJ{}
	if err := gconv.Scan(structI.Master, &structJ); err != nil {
		fmt.Printf("structI to structJ: %s\n", err.Error()) // 报错
	}
	fmt.Println("structJ.StructA.Superior:", structJ.StructA.Superior)       // structJ.StructA.Superior: superior100
	fmt.Println("structJ.Superior:", structJ.Superior)                       // structJ.Superior: <nil>
	fmt.Println("structJ.StructA.UpdatedTick:", structJ.StructA.UpdatedTick) // structJ.UpdatedTick: 30
	fmt.Println("structJ.UpdatedTick:", *structJ.UpdatedTick)                // structJ.UpdatedTick: 30

	// 测试10:结构嵌套,正常
	fmt.Println("测试10")
	structK := StructK{}
	yyy := NullID("superior100")
	structK.Master.Superior = &yyy
	structK.Master.UpdatedTick = 40
	structL := StructL{}
	if err := gconv.Scan(structK.Master, &structL); err != nil {
		fmt.Printf("structK to structL: %s\n", err.Error()) // 报错
	}
	fmt.Println("structL.StructA.Superior:", structL.StructA.Superior)       // structL.StructA.Superior: superior100
	fmt.Println("structL.Superior:", *structL.Superior)                      // structL.Superior: superior100
	fmt.Println("structL.StructA.UpdatedTick:", structL.StructA.UpdatedTick) // structJ.UpdatedTick: 30
	fmt.Println("structL.UpdatedTick:", *structL.UpdatedTick)                // structL.UpdatedTick: 30
}

What did you see happen?

参照上述单测结果

What did you expect to see?

向嵌套结构体中Scan数据时,内部不存在相同属性时,应该优先赋值外部同名属性,无论嵌套结构体的位置顺序如何

Metadata

Metadata

Assignees

Labels

bugIt is confirmed a bug, but don't worry, we'll handle it.doneThis issue is done, which may be release in next version.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions