Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gvar/Scan: When the field is a pointer, it does not take effect #4218

Open
806572349 opened this issue Mar 24, 2025 · 9 comments · May be fixed by #4224
Open

gvar/Scan: When the field is a pointer, it does not take effect #4218

806572349 opened this issue Mar 24, 2025 · 9 comments · May be fixed by #4224
Labels
bug It is confirmed a bug, but don't worry, we'll handle it.

Comments

@806572349
Copy link

Go version

1.24.1 linxu

GoFrame version

2.9.0

Can this bug be reproduced with the latest release?

Option Yes

What did you do?

Image
2.8.3 则没有这个问题

What did you see happen?

Image
这个是2.9.0升级过的后,children 指针没有返回。tree.GetVar(node.ParentId).scan(&parent)

What did you expect to see?

希望可以恢复到2.8.3的情况

@806572349 806572349 added the bug It is confirmed a bug, but don't worry, we'll handle it. label Mar 24, 2025
@Issues-translate-bot Issues-translate-bot changed the title gvar/Scan: 字段为指针时,不生效 gvar/Scan: When the field is a pointer, it does not take effect Mar 24, 2025
@806572349
Copy link
Author

806572349 commented Mar 24, 2025

type SysMenuVo struct {
	MenuId     int64        `json:"menuId"     orm:"menu_id"     ` // 菜单ID
	MenuName   string       `json:"menuName"   orm:"menu_name"   ` // 菜单名称
	Children   []*SysMenuVo `json:"children"   orm:"children"`     // 子菜单数
	ParentId   int64        `json:"parentId"   orm:"parent_id"   ` // 父菜单ID
	OrderNum   int          `json:"orderNum"   orm:"order_num"   ` // 显示顺序
	Path       string       `json:"path"       orm:"path"        ` // 路由地址
	Component  string       `json:"component"  orm:"component"   ` // 组件路径
	QueryParam string       `json:"queryParam" orm:"query_param" ` // 路由参数
	IsFrame    string       `json:"isFrame"    orm:"is_frame"    ` // 是否为外链(0是 1否)
	IsCache    string       `json:"isCache"    orm:"is_cache"    ` // 是否缓存(0缓存 1不缓存)
	MenuType   string       `json:"menuType"   orm:"menu_type"   ` // 菜单类型(M目录 C菜单 F按钮)
	Visible    string       `json:"visible"    orm:"visible"     ` // 显示状态(0显示 1隐藏)
	Status     string       `json:"status"     orm:"status"      ` // 菜单状态(0正常 1停用)
	Perms      string       `json:"perms"      orm:"perms"       ` // 权限标识
	Icon       string       `json:"icon"       orm:"icon"        ` // 菜单图标
	// CreateDept int64        `json:"createDept" orm:"create_dept" ` // 创建部门
	// CreateBy   int64        `json:"createBy"   orm:"create_by"   ` // 创建者
	CreateTime *gtime.Time `json:"createTime" orm:"create_time" ` // 创建时间
	// UpdateBy   int64        `json:"updateBy"   orm:"update_by"   ` // 更新者
	// UpdateTime *gtime.Time  `json:"updateTime" orm:"update_time" ` // 更新时间
	Remark string `json:"remark"     orm:"remark"      ` // 备注
}

@806572349
Copy link
Author

806572349 commented Mar 24, 2025

以下是最小可运行代码

package main

import (
	"fmt"

	"github.com/gogf/gf/v2/container/gmap"
	"github.com/gogf/gf/v2/encoding/gjson"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/os/gfile"
	"github.com/gogf/gf/v2/util/gconv"
)

func main() {
	menus := []*SysMenuVo{
		{
			MenuId:   1,
			MenuName: "系统管理",
			ParentId: 0,
		},
		{
			MenuId:   2,
			MenuName: "字典查询",
			ParentId: 1,
		},
	}

	var menuVoList = make([]*SysMenuVo, 0)
	for _, v := range menus {
		var vo *SysMenuVo
		gconv.Struct(v, &vo)
		vo.MenuId = v.MenuId
		menuVoList = append(menuVoList, vo)
	}
	root := BuildMenuTree(menuVoList)
	g.Dump(root)
}

// 辅助函数,用于将映射转换为树结构
func BuildMenuTree(routes []*SysMenuVo) (roots []*SysMenuVo) {
	var tree = BuildMenuMap(routes)
	for _, key := range tree.Keys() {
		var node *SysMenuVo
		nodeSysMenu := tree.GetVar(key)
		nodeSysMenu.Scan(&node)
		if node.ParentId == 0 {
			roots = append(roots, node)
		} else {
			var parent *SysMenuVo
			nodeParent := tree.GetVar(node.ParentId)
			nodeParent.Scan(&parent)
			fmt.Printf("parent.children=%p,%p\n", &parent.Children, &roots[0].Children)
			// parent := tree[node.ParentId]
			if parent != nil {
				if g.IsEmpty(parent.Children) {
					parent.Children = make([]*SysMenuVo, 0)
				}
				parent.Children = append(parent.Children, node)
				fmt.Println()
			}
		}
	}
	if len(roots) == 0 {
		return roots
	}
	return roots
}

// 构建树的函数
func BuildMenuMap(routes []*SysMenuVo) *gmap.ListMap {
	tree := gmap.NewListMap()
	for i := range routes {
		var res *SysMenuVo
		var item = routes[i]
		gconv.Scan(item, &res)
		// tree[item.MenuId] = res
		tree.Set(item.MenuId, res)
	}
	return tree
}

type SysMenuVo struct {
	MenuId   int64        `json:"menuId"     orm:"menu_id"     ` // 菜单ID
	MenuName string       `json:"menuName"   orm:"menu_name"   ` // 菜单名称
	Children []*SysMenuVo `json:"children"   orm:"children"`     // 子菜单数
	ParentId int64        `json:"parentId"   orm:"parent_id"   ` // 父菜单ID
}

这个是2.8.3 的代码
然后手动改下升级到2.9.0的版本

两次运行结果不一致

@gogf gogf deleted a comment from Issues-translate-bot Mar 25, 2025
@wln32
Copy link
Member

wln32 commented Mar 25, 2025

@806572349 @gqcn 在2.8.3中,&parent.Children和&roots[0].Children两者地址一致,你在向parent.Children中添加时,其实就是在向roots[0].Children添加,2.9.0,两者地址不一致,所以在parent.Children中添加,roots[0].Children并不会更新,具体可以运行代码,看BuildMenuTree函数里面的打印即可

@Issues-translate-bot
Copy link

Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑‍🤝‍🧑👫🧑🏿‍🤝‍🧑🏻👩🏾‍🤝‍👨🏿👬🏿


@806572349 @gqcn In 2.8.3, the addresses of &parent.Children and &roots[0].Children are the same. When you add to parent.Children, you are actually adding to roots[0].Children, 2.9.0. The addresses of the two are inconsistent, so when adding to parent.Children, roots[0].Children will not be updated.

@806572349
Copy link
Author

  1. BuildMenuMap 返回的是一个*gmap.ListMap , value存的是指针,那为啥取值的时候,会返回一个新的地址呢?在我看来这应该是一样的。
  2. 换成官方的map是没有这种情况的。
  3. gf 2.9.0这种做法是不是可以理解为ListMap 每次get返回gvar进行scan的时候,返回都是属性值相同的新地址吗?但我存的不是指针吗?
  4. 如果gf 定义的*gmap.ListMap,不能返回原始指针地址,希望在2.9.0官方文档说明一下。这种情况很容易造成误解。

@Issues-translate-bot
Copy link

Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑‍🤝‍🧑👫🧑🏿‍🤝‍🧑🏻👩🏾‍🤝‍👨🏿👬🏿


  1. BuildMenuMap returns a *gmap.ListMap, and the value stores a pointer. So why does a new address be returned when taking the value? It should be the same in my opinion.
  2. This is not the case when it is replaced with an official map.
  3. Can gf 2.9.0 be understood as ListMap Every time when get returns gvar for scan, is it a new address with the same attribute value? But isn't I a pointer?
  4. If the *gmap.ListMap defined by gf cannot return the original pointer address, I hope it will be explained in the official document 2.9.0. This situation can easily lead to misunderstandings.

@gqcn
Copy link
Member

gqcn commented Mar 25, 2025

最小化复现代码:

package main

import (
	"fmt"

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

type SysMenuVo struct {
	MenuId   int64        `json:"menuId"     orm:"menu_id"     ` // 菜单ID
	MenuName string       `json:"menuName"   orm:"menu_name"   ` // 菜单名称
	Children []*SysMenuVo `json:"children"   orm:"children"`     // 子菜单数
	ParentId int64        `json:"parentId"   orm:"parent_id"   ` // 父菜单ID
}

func main() {
	menus := []*SysMenuVo{
		{
			MenuId:   1,
			MenuName: "系统管理",
			ParentId: 0,
		},
		{
			MenuId:   2,
			MenuName: "字典查询",
			ParentId: 1,
		},
	}
	var parent *SysMenuVo
	err := gconv.Scan(menus[0], &parent)
	if err != nil {
		panic(err)
	}
	fmt.Printf("parent.children=%p,%p\n", &parent.Children, &menus[0].Children)
	parent.Children = append(parent.Children, menus[1])
	g.Dump(menus)
}

@wln32
Copy link
Member

wln32 commented Mar 26, 2025

@gqcn 应在文档或注释中说明当类型相同时,转换的逻辑,根据目前支持的参数类型,可以分为以下
src可以取struct,*struct
dst可以取*struct,**struct
可以得到以下几种组合

  1. src=struct, dst=*struct
  2. src=struct, dst=**struct
  3. src=*struct, dst=*struct
  4. src=*struct, dst=**struct

第1 2种情况只能是值赋值,修改dst时,不会影响src,所以children=nil
第3种情况由于要设置dst,所以用的时候会调用dst.Elem(),dst=struct,src需要匹配dst的类型,也会变成struct,所以children=nil
第4种情况,dst.Elem()之后,dst仍然是*struct,刚好和src类型匹配,相当于直接把指针赋值给dst,对dst做的影响都会影响到src
所以children !=nil

综上,前3种情况的转换,children=nil,只有第4种children!=nil
注: 以上所说情况在2.8.3版本中,2.9.0中4种情况都是children=nil

还有,也需要补充对于字段类型相同时,如果是指针类型,需说明是值赋值,还是指针赋值

@Issues-translate-bot
Copy link

Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑‍🤝‍🧑👫🧑🏿‍🤝‍🧑🏻👩🏾‍🤝‍👨🏿👬🏿


@gqcn should be explained in the document or comments when the type is the same, the logic of the conversion can be divided into the following according to the currently supported parameter types.
src can take struct, *struct
dst can take *struct, **struct
The following combinations can be obtained

  1. src=struct, dst=*struct
  2. src=struct, dst=**struct
  3. src=*struct, dst=*struct
  4. src=*struct, dst=**struct

The 1st and 2nd case can only be value assignment. When modifying dst, src will not affect src, so children=nil
In the third case, since dst is set, dst.Elem() will be called when using it, dst=struct, src needs to match the type of dst, and it will also become struct, so children=nil
In the fourth case, after dst.Elem(), dst is still *struct, which just matches the src type, which is equivalent to directly assigning the pointer to dst. The impact on dst will affect src
So children !=nil

In summary, children=nil for the first three situations, only the fourth child!=nil
Note: The above situations are in version 2.8.3, and in version 2.9.0, children=nil

Also, it is also necessary to add that if the field type is the same, if it is a pointer type, it is necessary to specify whether it is a value assignment or a pointer assignment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug It is confirmed a bug, but don't worry, we'll handle it.
Projects
None yet
4 participants