Go语言匿名字段使用与注意事项

news2024/9/29 22:42:38

1. 定义

Go语言支持一种特殊的字段只需要提供类型而不需要写字段名的字段称之为匿名字段或者嵌套字段

所谓匿名字段实际上是一种结构体嵌套的方式,所以也可以称作嵌套字段

这种方式可以实现组合复用,即通过匿名字段,结构体可以直接访问嵌套结构体的字段和方法,而无需通过字段名或类型进行嵌套。这些方法和属性被称为“提升”的方法和属性。通过类型名称也可以直接访问匿名嵌入字段。

2.代码示例

2.1 简单示例

package main

import (
	"fmt"
)

type Person struct {
	Name  string
	Age   int
	Phone string
}

func (p *Person) playBasketball() {
	fmt.Println("打篮球...")
}

type Employee struct {
	Person
	EmployeeId int
}

// 测试匿名字段
func TestAnonymous() {
	emp := Employee{
		Person: Person{
			Name:  "Liu",
			Age:   20,
			Phone: "18899999999",
		},
		EmployeeId: 101,
	}
	//  可直接使用emp调用嵌套类型的方法
	emp.playBasketball()

	fmt.Println("id:  ", emp.EmployeeId)
	//  可直接使用emp打印出嵌套类型的所有字段
	fmt.Println("name:  " + emp.Name)
	fmt.Println("age:  ", emp.Age)
	fmt.Println("name:  " + emp.Phone)
	// 	通过匿名类型名来访问
	fmt.Println("类型访问的id:  ", emp.Person.Name)
}

func main() {
	TestAnonymous()
}

2.2 嵌套类型有重复字段

在上面的例子中,Employee 结构体嵌套了 Person 结构体,通过这种方式,Employee 可以直接访问 Person 的字段和方法,而无需使用类似 emp.Person.Name这样的方式。

需要注意的是,如果结构体中有多个 匿名字段,并且它们拥有相同的字段名,那么在访问这个同名字段时,需要指定嵌套结构体的类型,以避免歧义。例如:

package main

import (
	"fmt"
)

type Person struct {
	Name  string
	Age   int
	Phone string
}

func (p *Person) contact() {
	fmt.Println("Person联系...")
}

// 合同
type Contract struct {
	EmployeeId int
	Phone      string
}

func (p *Contract) contact() {
	fmt.Println("Contract联系...")
}

type Employee struct {
	Person
	EmployeeId int
	Contract
}

// 测试匿名字段
func TestAnonymous() {
	emp := Employee{
		Person: Person{
			Name:  "Liu",
			Age:   20,
			Phone: "18899999999",
		},
		EmployeeId: 101,
		Contract: Contract{
			EmployeeId: 101,
			Phone:      "16699999999",
		},
	}
	//  多个匿名类型字段的字段名称可以相同,这样在访问时需要通过匿名类型名来访问
	//emp.contact() //会报错
	emp.Person.contact()
	emp.Contract.contact()

	fmt.Println("id:  ", emp.EmployeeId)
	//  可直接使用emp打印出嵌套类型的所有字段
	fmt.Println("name:  " + emp.Name)
	fmt.Println("age:  ", emp.Age)
	//fmt.Println("name:  " + emp.Phone)//这里会报错,因为Person和Contract中都有Phone字段
	fmt.Println("person phone:  ", emp.Person.Phone)
	fmt.Println("contract phone:  ", emp.Contract.Phone)
}

func main() {
	TestAnonymous()
}

在这个例子中,Person 和 Contract 都有 Phone 字段,因此在访问时需要指定具体的类型以避免歧义。
同样的,Person 和 Contact 都有 contact 方法,因此在访问时也需要指定具体的类型以避免歧义。
如果不指定则会编译报错.

3. 结构体匿名字段的Json序列化、反序列化

结构体序列化规则

@注意:可导出的字段(首字母大写),才能参与Json的序列化

标签json的key
有标签,json:"xx"key=xx
无标签key=结构体原属性字段
有标签,json:"-"会被忽略,不参与序列化
有标签,json:"xxx,omitempty"代表该字段为空值时,会被忽略。其中xxx可以省略,,不可以省略。
如:json:",omitempty"
有标签,json:"xxx,string"代表输出类型会转化为字符串。其中xxx也可以省略
它只适用于字符串、浮点数、整数类型的字段

3.1  代码示例

package main

import (
	"encoding/json"
	"fmt"
)

type Student struct {
	// 指定json标签时,序列化的key为标签值:name
	Name string `json:"name"`
	// 不指定序列化标签时,key为原属性:Age
	Age int
	// 当标签值为`json:"-"`,代表改字段会被忽略
	Home string `json:"-"`
	// 标签指定omitempty选项,代表该字段为空值时,会被忽略
	Phone string `json:"phone,omitempty"`
	// 标签指定string选项,代表输出类型会转化为字符串
	// 它只适用于字符串、浮点数、整数类型的字段
	Score float64 `json:"score,string"`
}

func TestMarshal() {
	// 声明初始化结构体
	student1 := Student{
		Name:  "Liu",
		Age:   18,
		Home:  "北京",
		Score: 90.5,
		Phone: "",
	}
	// 序列化
	json1, _ := json.Marshal(student1)
	fmt.Printf("序列化json:%s\n", json1)
}
func main() {
	TestMarshal()
}

 输出结果:

序列化json:{"name":"Liu","Age":18,"score":"90.5"}

3.2 匿名字段序列化

3.2.1 无JSON标签

a. 字段标签不重复

School.Name和Student.Name,Json标签不一致。

package main

import (
	"encoding/json"
	"fmt"
)

// 学校
type School struct {
	Name    string `json:"schoolName"`
	Address string `json:"schoolAddress"`
}

// 学生
type Student struct {
	Name string `json:"name"`
	// 匿名字段,而且没有json标签
	School
}

// 序列化-匿名字段 (默认字段不冲突)
func TestAnonymousTagDifferent() {
	var student = Student{
		Name: "XiaoMing",
		School: School{
			Name:    "北京大学",
			Address: "北京海淀区",
		},
	}
	jsonByte, _ := json.Marshal(student)
	fmt.Printf("json: %s \n", jsonByte)
}
func main() {
	TestAnonymousTagDifferent()
}

结果:

json: {"name":"XiaoMing","schoolName":"北京大学","schoolAddress":"北京海淀区"} 
b. 字段标签重复

School.Name和Student.Name,Json标签一致,都是 json:"name"

package main

import (
	"encoding/json"
	"fmt"
)

// 学校
type School struct {
	Name    string `json:"name"`
	Address string `json:"schoolAddress"`
}

// 学生
type Student struct {
	Name string `json:"name"`
	// 匿名字段,而且没有json标签
	School
}

// 序列化-匿名字段 (默认字段冲突)
func TestAnonymousTagDifferent() {
	var student = Student{
		Name: "XiaoMing",
		School: School{
			Name:    "北京大学",
			Address: "北京海淀区",
		},
	}
	jsonByte, _ := json.Marshal(student)
	fmt.Printf("json: %s \n", jsonByte)
}
func main() {
	TestAnonymousTagDifferent()
}

结果:

json: {"name":"XiaoMing","schoolAddress":"北京海淀区"} 

根据上面代码,得知如果字段标签冲突,冲突的匿名字段会被忽略。

3.2.2 有JSON标签

当匿名字段设置json标签时, 就不会出现冲突的情况,因为序列化后的匿名字段会变成对象。

package main

import (
	"encoding/json"
	"fmt"
)

// 学校
type School struct {
	Name    string `json:"name"`
	Address string `json:"schoolAddress"`
}

// 学生
type Student struct {
	Name string `json:"name"`
	// 匿名字段,而且没有json标签
	School `json:"school"`
}

// 序列化-匿名字段 (默认字段冲突)
func TestAnonymousTagDifferent() {
	var student = Student{
		Name: "XiaoMing",
		School: School{
			Name:    "北京大学",
			Address: "北京海淀区",
		},
	}
	jsonByte, _ := json.Marshal(student)
	fmt.Printf("json: %s \n", jsonByte)
}
func main() {
	TestAnonymousTagDifferent()
}

结果:

json: {"name":"XiaoMing","school":{"name":"北京大学","schoolAddress":"北京海淀区"}} 

对比前面两个代码可以发现 当匿名字段设置json标签时,序列化后的匿名字段会变成对象 

3.3 匿名字段反序列化

3.3.1 无JSON标签

a. 字段标签不重复
package main

import (
	"encoding/json"
	"fmt"
)

// 学校
type School struct {
	Name    string `json:"schoolName"`
	Address string `json:"schoolAddress"`
}

// 学生
type Student struct {
	Name string `json:"name"`
	// 匿名字段,而且没有json标签
	School
}

// 反序列化-匿名字段 (默认字段不冲突)
func TestUnMarshal() {
	jsonStr := `{"name":"XiaoMing","schoolName":"北京大学","schoolAddress":"北京海淀区"}`
	stu := Student{}
	err := json.Unmarshal([]byte(jsonStr), &stu)
	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Printf("反序列化结果:%+v", stu)
	fmt.Println()
}
func main() {
	TestUnMarshal()
}

结果:

反序列化结果:{Name:XiaoMing School:{Name:北京大学 Address:北京海淀区}}
b. 字段标签重复
package main

import (
	"encoding/json"
	"fmt"
)

// 学校
type School struct {
	Name    string `json:"name"`
	Address string `json:"schoolAddress"`
}

// 学生
type Student struct {
	Name string `json:"name"`
	// 匿名字段,而且没有json标签
	School
}

// 反序列化-匿名字段 (默认字段冲突)
func TestUnMarshal() {
	jsonStr := `{"name":"XiaoMing","schoolAddress":"北京海淀区"}`
	stu := Student{}
	err := json.Unmarshal([]byte(jsonStr), &stu)
	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Printf("反序列化结果:%+v", stu)
	fmt.Println()
}
func main() {
	TestUnMarshal()
}

结果:

反序列化结果:{Name:XiaoMing School:{Name: Address:北京海淀区}}

其中如果上面示例中将jsonStr改为如下

jsonStr := `{"name":"XiaoMing","name":"北京大学","schoolAddress":"北京海淀区"}`

那么结果如下:可以看到Name的值变了,但是School中的依然没有值

反序列化结果:{Name:北京大学 School:{Name: Address:北京海淀区}}

从上面示例中可以看到 当字段标签重复时,反序列化会优先给主属性字段赋值。

3.3.2 有JSON标签

示例代码:
package main

import (
	"encoding/json"
	"fmt"
)

// 学校
type School struct {
	Name    string `json:"name"`
	Address string `json:"schoolAddress"`
}

// 学生
type Student struct {
	Name string `json:"name"`
	// 匿名字段,而且没有json标签
	School `json:"school"`
}

// 反序列化-匿名字段 (默认字段冲突)
func TestUnMarshal() {
	jsonStr := `{"name":"XiaoMing","name":"北京大学","schoolAddress":"北京海淀区"}`
	stu := Student{}
	err := json.Unmarshal([]byte(jsonStr), &stu)
	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Printf("反序列化结果:%+v", stu)
	fmt.Println()

	jsonStr2 := `{"name":"XiaoMing","school":{"name":"北京大学","schoolAddress":"北京海淀区"}} `
	stu2 := Student{}
	err = json.Unmarshal([]byte(jsonStr2), &stu2)
	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Printf("2反序列化结果:%+v", stu2)
	fmt.Println()
}
func main() {
	TestUnMarshal()
}

结果:

反序列化结果:{Name:北京大学 School:{Name: Address:}}
2反序列化结果:{Name:XiaoMing School:{Name:北京大学 Address:北京海淀区}}

3.4 匿名字段json总结

3.4.1 序列化

a. 匿名字段无标签
  • 当匿名字段没有指定标签时,序列化后的结构为同级,如{"..":"..",..}

  • 当匿名属性和主属性的字段标签一样时,序列化会忽略匿名属性的字段。

  • 当匿名属性和主属性的字段标签不一样时,序列化不忽略任何字段。

b. 匿名字段有标签
  • 当匿名字段a指定标签时,序列化后的结构为上下级,如{"..":"..","a":{"xx":"xx"}}

  • 匿名属性和主属性字段,标签无论是否一致,序列化都不会忽略任何字段。

4.2 反序列化

a. 匿名字段无标签
  • 当匿名字段没有指定标签时,可解析的JSON结构,为: {"..":"..",..}

  • 当匿名属性和主属性的字段标签一样时,会优先将值赋给主属性,匿名属性为类型零值。

  • 当匿名属性和主属性的字段标签不一样时,会正常解析。

b. 匿名字段有标签
  • 当匿名字段指定标签时,可解析的JSON结构,为: {"..":"..","a":{"xx":"xx"}}

  • 匿名属性和主属性字段,标签无论是否一致,反序列化都能正常赋值。

当结构体中嵌套匿名结构体字段时,在进行序列化和反序列时,推荐为匿名字段加上json标签。

图片

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2178007.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

开放原子开源基金会网站上的开源项目Opns存在缓冲区溢出缺陷

最近在开放原子开源基金会网站上,看到一些开源项目,之前分析出华为的鸿蒙操作系统代码,没有发现有价值的安全漏洞。现在,下载上面的Onps开源网络协议栈,既然是通讯所使用的软件,其质量应该值得信任呢&#…

LeetCode[中等] 238. 除自身以外数组的乘积

给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O(n) 时间复杂…

828华为云征文 | 云服务器Flexus X实例:向量数据库 pgvector 部署,实现向量检索

目录 一、什么是向量数据库 pgvector ? 二、pgvector 部署 2.1 安装 Docker 2.2 拉取镜像 2.3 添加规则 三、pgvector 运行 3.1 运行 pgvector 3.2 连接 pgvector 3.3 pgvector 常见操作 四、总结 本篇文章通过 云服务器Flexus X实例 部署向量数据库 pgve…

什么情况?上交所服务器被你们给买崩了?

号主:老杨丨11年资深网络工程师,更多网工提升干货,请关注公众号:网络工程师俱乐部 上午好,我的网工朋友。 9月27日早上,A股市场迎来了一波前所未有的火爆行情,成交量激增,市场情绪高…

51单片机的光照强度检测【proteus仿真+程序+报告+原理图+演示视频】

1、主要功能 该系统由AT89C51/STC89C52单片机LCD1602显示模块光照传感器按键蜂鸣器LED等模块构成。适用于光照强度检测、光照强度测量报警等相似项目。 可实现功能: 1、LCD1602实时显示光照强度信息 2、光照强度传感器(电位器模拟)采集光照信息 3、可…

鸿蒙开发(NEXT/API 12)【硬件(振动开发1)】振动

通过最大化开放马达器件能力,振动器模块服务拓展了原生马达服务,实现了振动与交互融合设计,从而打造出细腻精致的一体化振动体验和差异化体验,提升用户交互效率、易用性以及用户体验,并增强品牌竞争力。 运作机制 Vi…

小程序原生-地理位置授权用户拒绝后的解决方案

在开发的过程中,我们会发现一个问题: 在调用 wx.getLocation() 获取用地理位置时,如果用户选择拒绝授权,代码会直接抛出错误。在拒绝授权以后,再次调用 wx.getLocation() 时,就不会在弹窗询问用户是否允许…

【国庆要来了】基于Leaflet的旅游路线WebGIS可视化实践

前言 转眼2024年的国庆节马上就要来临了,估计很多小伙伴都计划好了旅游路线。金秋十月,不管是选择出门去看看风景,还是选择在家里看人。从自己生活惯了的城市去别人生活惯了的城市,去感受城市烟火、去感受人文风景,为2…

Windows环境 源码编译 FFmpeg

记录一下windows环境纯代码编译ffmeg的过程! 目录 一、安装MSYS2 1.下载安装 2.配置 3.修改源 4.测试与更新 二、安装其他必要工具 1.安装MinGW-w64 2.安装git 3..安装make等工具 4.编译前的其他准备工作 ①.重命名link.exe ②.下载和安装YASM ③.安装…

ADC的原理

一、介绍 模数转换,即Analog-to-Digital Converter,常称ADC,是指将连续变量的模拟信号转换为离散的数字信号的器件,比如将模温度感器产生的电信号转为控制芯片能处理的数字信号0101,这样ADC就建立了模拟世界的传感器和…

tomcat服务器控制台乱码(超简单,其中一种方式)

一、问题 为解决前: 是这样子,控制台中文乱码 解决后: 二、解决 在tomcat的安装目录下,找到conf ---》logging.properties 点击去进行编辑 重点!!! 编辑完之后,保存 如何返回到…

文献阅读9.29

目录 基于物理的神经网络在河流淤积模拟中的应用 文献摘要 讨论|结论 理论介绍 PINN 实验方程 Ansys中的数学模型 实验设置 基于物理的神经网络在河流淤积模拟中的应用 文献摘要 本文从水动力学和污染物运移动力学的基本原理出发,全面探讨了用于研究泥沙淤…

六、动画系统

一、动画事件 在此处实现:动画播放至此处,人物角色移动 1、添加动画事件 选择对应的动画 右键动画部分,选择添加动画事件 在人物身上挂载了对应的动画系统,需要在代码中,设置一个函数,作为动画事件 执行…

【ADC】使用仪表放大器驱动 SAR 型 ADC 时的输入输出范围

概述 本文学习于TI 高精度实验室课程,介绍使用仪表放大器时 SAR ADC 驱动放大器的注意事项。具体包括:介绍如何使用仪表放大器设计数据转换器驱动电路。 仪表放大器(Instrumentation Amplifier,下文简称 INA)可抑制输…

自定义knife4j访问路径

文章目录 本文档只是为了留档方便以后工作运维,或者给同事分享文档内容比较简陋命令也不是特别全,不适合小白观看,如有不懂可以私信,上班期间都是在得 原由,嫌弃doc.html 太大众 直接重定向,直接上代码了 p…

【Linux】Linux内核结构基础

Linux内核结构基础 一、Linux内核结构介绍二、Linux内核结构☆ Linux内核结构框图Linux系统架构 三、认识驱动为什么学驱动文件名与设备号open函数过程实例详解 四、shellshell脚本 参考博文: 【Linux】内核结构(全)linux内核结构介绍&#…

理解Python闭包概念

闭包并不只是一个python中的概念,在函数式编程语言中应用较为广泛。理解python中的闭包一方面是能够正确的使用闭包,另一方面可以好好体会和思考闭包的设计思想。 1.概念介绍 首先看一下维基上对闭包的解释: 在计算机科学中,闭包…

【动态规划-分组背包】【hard】力扣2218. 从栈中取出 K 个硬币的最大面值和

一张桌子上总共有 n 个硬币 栈 。每个栈有 正整数 个带面值的硬币。 每一次操作中,你可以从任意一个栈的 顶部 取出 1 个硬币,从栈中移除它,并放入你的钱包里。 给你一个列表 piles ,其中 piles[i] 是一个整数数组,分…

OpenEuler虚拟机安装保姆级教程 | 附可视化界面

0x00 系统介绍 在 2019 年 7 月 19 日,华为宣布要在年底正式开源 openEuler 操作系统;在半年后的 12 月 31 日,华为正式开源了 openEuler 操作系统,邀请社区开发者共同来贡献。 一年后,截止到 2020 年12 月 25日&…

注册中心Eureka

Eureka 文章目录 前言一、Eureka是什么?二、使用步骤1.搭建一个SpringCloud项目2.启动类上增加注解 EnableEurekaServer3.启动项目 访问 配置文件里定义的端口号4.启动成功访问 localhost:70005.以同样的方式创建子项目 eureka-client-xx 作为 服务客户端 然后向eur…