网站模板版权网址导航网站有哪些
2025/12/31 5:42:27 网站建设 项目流程
网站模板版权,网址导航网站有哪些,网站建设工作任务,东莞网页设计培训中心是我的老朋友#xff0c;上份工作开发 web 应用时就作为前后端数据交流的协议#xff0c;现在也是用 json 数据持久化到数据库。虽然面熟得很但还远远达不到知根知底#xff0c;而且在边界的探索上越发束手束脚。比如之前想写一个范型的结构提高通用性#xff0c;但是不清楚…是我的老朋友上份工作开发 web 应用时就作为前后端数据交流的协议现在也是用 json 数据持久化到数据库。虽然面熟得很但还远远达不到知根知底而且在边界的探索上越发束手束脚。比如之前想写一个范型的结构提高通用性但是不清楚对范型的支持如何思来想去还是用了普通类型还有项目中的规范不允许使用指针类型的字段存储我一直抱有疑问。归根结底还是不熟悉 json 编解码的一些特性导致我不敢尝试也不敢使用生怕出了问题。所以近些日子也是狠狠研究了一把补习了很多之前模棱两可的概念。有一句话说的好“多和旧人做新事”我想我和 json 大概也属于这种关系吧json 解析时字段名称保持一致这个疑问是假如我们编码不太规范不给字段添加 Tag序列化和反序列化后的字段字符串会是什么type Object struct {ID stringVaLuE2T int64}func TestFunc(t *testing.T) {obj : Object{ID: the-id,VaLuE2T: 7239,}marshal, err : json.Marshal(obj)assert.Nil(t, err)fmt.Println(string(marshal))}{ID:the-id,VaLuE2T:7239}用代码验证的结果是json 编码并不会将程序中定义的字段名称改成驼峰或者什么特殊大小写规则而是完完全全使用原本的字符。如果是我目前的这个需求即仅用来保存数据编码和解码都在后端进行那这样完全可用不需要考虑更多但如果是需要前后端数据对齐而且有特殊的字段名称规范那就要使用 tag 对编码字段进行规定比如下方的代码。type Object struct {ID string json:idVaLuE2T int64 json:value2t}func TestFunc(t *testing.T) {obj : Object{ID: the-id,VaLuE2T: 7239,}marshal, err : json.Marshal(obj)assert.Nil(t, err)fmt.Println(string(marshal))}{id:the-id,value2t:7239}但这只是编码对于解码来说是大小写不敏感的就算传过来的是某种形式的妖魔鬼怪也可以解析出来比如type Object struct {CaSeTesT stringCAsEteSt string}func TestFunc(t *testing.T) {newObj : Object{}testString : {cAsEteSt:test}err : json.Unmarshal([]byte(testString), newObj)assert.Nil(t, err)fmt.Println(CaSeTesT:, newObj.CaSeTesT, CAsEteSt:, newObj.CAsEteSt)}CaSeTesT: test CAsEteSt:也因为如此最好不要在相关结构体里定义名称相同的字段即便有大小写的区别也会导致不可预料的情况发生。而且严格按照驼峰格式命名的话不存在大小写区别相同字母的字段就是唯一的。而 Go 团队也将在 json/v2 中默认大小写敏感规范的行为肯定会带来更少的 bug ~ 关于 json/v2 具体可以参考A new experimental Go API for JSON。哦哦还有一点如果不想某个字段参与解码编码可以使用特殊的 tag。type Object struct {Value string json:-}可以编解码接口和范型我们知道 json 官方包底层是依靠反射实现的所以获取到传入接口的结构体类型不是问题就可以使用原结构体类型去编解码所以只要是 Golang 支持的类型都可以甚至是范型。当然也有一些反例需要注意比如 func 这种类型就不行。type Object struct {Func func()}func TestFunc(t *testing.T) {obj : Object{Func: func() {},}marshal, err : json.Marshal(obj)fmt.Println(err)}json: unsupported type: func()omitempty 和字段类型当字段是结构体类型的那么 omitempty 无效。当字段是指针类型的如果值是 nil那么有 omitempty 就不进行编码没有 omitempty 会编码成 null。经过测试不仅是指针类型的结构体指针类型的基础类型比如 string 或者 int64 也是如此。type Object struct {TheStructO AObject json:theStructO,omitemptyTheStruct AObject json:theStructThePointO *AObject json:thePointO,omitemptyThePoint *AObject json:thePoint}type AObject struct {Values interface{}}func TestFunc(t *testing.T) {obj : Object{}marshal, err : json.Marshal(obj)assert.Nil(t, err)fmt.Println(string(marshal))}{theStructO:{Values:null},theStruct:{Values:null},thePoint:null}结构体类型和指针类型性能比较使用 Benchmark 测试结构体类型和指针类型的性能。结论是在 CPU 性能上两者差不多但是一个指针类型的字段会多进行一次内存分配在一定程度上增加了 GC 的压力所以看起来小的结构体还是结构体值类型更合适。type ObjectStruct struct {TheStruct AObject json:theStruct}type ObjectPoint struct {TheStruct *AObject json:theStruct}func BenchmarkFunc(b *testing.B) {data : []byte({theStruct:{valueString:text,valueInt:123,valueFloat:3.14}})b.Run(unmarshal-struct, func(b *testing.B) {for i : 0; i b.N; i {_ json.Unmarshal(data, ObjectStruct{})}})b.Run(unmarshal-point, func(b *testing.B) {for i : 0; i b.N; i {_ json.Unmarshal(data, ObjectPoint{})}})}BenchmarkFuncBenchmarkFunc/unmarshal-structBenchmarkFunc/unmarshal-struct-8 457996 2518 ns/op 304 B/op 8 allocs/opBenchmarkFunc/unmarshal-pointBenchmarkFunc/unmarshal-point-8 471489 2517 ns/op 312 B/op 9 allocs/opPASS自定义 json 编解码方式可以实现 json 规定的接口使结构体执行特定的编解码方式假设下面一种情况我希望业务代码开发中使用方便查询和操作的map然后存储或者通讯使用占用空间更少的数组或者切片但同时我又不想增加开发人员的心智负担想要之前怎么使用现在就如何使用或者无法更改一些库的执行方式只能绕路。也就是说平时开发时需要直接调用 json.Marshal 或 json.UnMarshal而不需要额外操作这时就可以通过实现接口的方式达成目的见如下代码。type Object struct {UserMap map[string]struct{}}func (o Object) MarshalJSON() ([]byte, error) {list : make([]string, 0, len(o.UserMap))for key : range o.UserMap {list append(list, key)}return json.Marshal(list)}func (o *Object) UnmarshalJSON(b []byte) error {var list []stringerr : json.Unmarshal(b, list)if err ! nil {return err}o.UserMap make(map[string]struct{}, len(list))for i : range list {o.UserMap[list[i]] struct{}{}}return nil}type ObjectNormal struct {UserMap map[string]struct{}}func TestFunc(t *testing.T) {userMap : map[string]struct{}{user1: {},user2: {},user3: {},}obj1 : Object{UserMap: userMap,}obj2 : ObjectNormal{UserMap: userMap,}marshal1, err : json.Marshal(obj1)assert.Nil(t, err)fmt.Println(len:, len(marshal1), string(marshal1))marshal2, err : json.Marshal(obj2)assert.Nil(t, err)fmt.Println(len:, len(marshal2), string(marshal2))}len: 25 [user1,user2,user3]len: 46 {UserMap:{user1:{},user2:{},user3:{}}}此处还有一个小 TipsUnmarshalJSON 用指针接收器没问题因为需要修改调用这个方法的结构体的字段值但是 MarshalJSON 尽量用值接收器因为这样在调用 json.Marshal 时无论传入的是值还是指针都能正常编码同时也避免了传入的是 nil 导致 panic。被遗忘在角落的 gob在 golang 源码的 encoding 包下有很多编解码方式比如 json、xml、base64 等等但其中也有一个 gob假如你之前没有接触过 golang 这门编程语言那你大概率没有听说过这种编码解码方式因为它就独属于 golang其他语言基本上可以说无法解析。type G struct {Value string}func TestGOB(t *testing.T) {g : G{Value: hello}var buf bytes.Bufferenc : gob.NewEncoder(buf)if err : enc.Encode(g); err ! nil {panic(err)}fmt.Println(Gob encoded bytes:, buf.Bytes())var decoded Gdec : gob.NewDecoder(buf)if err : dec.Decode(decoded); err ! nil {panic(err)}fmt.Println(Decoded struct:, decoded)}使用方式大差不差但与 json 的行为相比需要依赖 bytes.Buffer也正因如此可以连续向 Buffer 编码多个结构体然后连续解码多个结构体。此外和 json 一样也可以实现特定的接口来自定义编解码行为具体可以参考https://pkg.go.dev/encoding/gob。向 json 和 xml 这种编码方式方便让我们肉眼观察但因此也牺牲了性能和空间而 gob 类似 protobuf 都是生成二进制但是 gob 仅存在于 golang 生态中普及度远远不及可以生成多种语言代码的 protobuf。type User struct {Name string}func Benchmark(b *testing.B) {b.Run(gob, func(b *testing.B) {var buf bytes.Bufferenc : gob.NewEncoder(buf)dec : gob.NewDecoder(buf)user : User{Name: hello}for i : 0; i b.N; i {_ enc.Encode(user)_ dec.Decode(user)}})b.Run(json, func(b *testing.B) {user : User{Name: hello}for i : 0; i b.N; i {marshal, _ : json.Marshal(user)_ json.Unmarshal(marshal, user)}})b.Run(protobuf, func(b *testing.B) {user : ttt.User{Name: hello}for i : 0; i b.N; i {data, _ : proto.Marshal(user)_ proto.Unmarshal(data, user)}})}控制变量法我设计了相同的结构体 proto。message User {string Name 1;}BenchmarkBenchmark/gobBenchmark/gob-8 1230975 954.7 ns/op 32 B/op 3 allocs/opBenchmark/jsonBenchmark/json-8 1000000 1130 ns/op 256 B/op 7 allocs/opBenchmark/protobufBenchmark/protobuf-8 2500924 483.2 ns/op 16 B/op 2 allocs/opPASS可能是由于我用的是简单结构体gob 和 json 在 CPU 性能上并没有看到什么差距但是内存分配差了蛮多如果不考虑通用性和扩展性的话gob 也是个不错的选择虽然事实是这两方面不可能不考虑。而且在性能方面也远远不及代码生成派生产实践中多多用 protobuf 才是正道。RawMessage 的应用场景试想这样一种情况某个推荐业务有两层分别是 A 和 B 通常是是 A 调用 B 的接口(RPC)然后 A 再组织数据发给前端QA和运营需求要获取到 B 持有的信息用来 debug 和测试这个时候因为是不关键的 debug 信息所以也就懒得定义消息结构体而是直接在B中用 json 将数据序列化成字符串传给 A然后 A 在外面封装一层错误码和数据传给前端如果直接这么操作会有一个问题type ResponseB struct {Name string}type ResponseA struct {Data string}func TestRaw(t *testing.T) {r : ResponseB{Name: hello-world,}marshal, err : json.Marshal(r)assert.Nil(t, err)ra : ResponseA{Data: string(marshal),}marshal2, err : json.Marshal(ra)assert.Nil(t, err)fmt.Println(string(marshal), string(marshal2))}{Name:hello-world} {Data:{\Name\:\hello-world\}}字符串类型的字段在 json.Marshal 时其中的双引号会被转义甚至于三层四层来回传递后转移符号会越来越多。所以这个时候就可以使用 json.RawMessage。type ResponseB struct {Name string}type ResponseA struct {Data json.RawMessage}func TestRaw(t *testing.T) {r : RawStruct{Name: hello-world,}marshal, err : json.Marshal(r)assert.Nil(t, err)rj : RawJson{Data: json.RawMessage(marshal),}marshal3, err : json.Marshal(rj)assert.Nil(t, err)fmt.Println(string(marshal), string(marshal3))}{Name:hello-world} {Data:{Name:hello-world}}除了编码之外解码时的 RawMessage 也有大用处尤其是需要二次解码的情况。比如有一个接口是聊天室发送消息然后消息有不同的类型每个类型的内容的结构都不一样这时需要先解码通用结构然后拿到消息类型再根据消息类型解码具体消息内容。比如下面这个例子如果不使用 RawMessage就一定要在字符串内增加转义。type Inside struct {Name string}type Outside struct {Data interface{}DataString stringDataRaw json.RawMessage}func TestRaw(t *testing.T) {data : {Data:{Name:hello-world},DataString:{Name:hello-world},DataRaw:{Name:hello-world}}rj : Outside{}err : json.Unmarshal([]byte(data), rj)assert.Nil(t, err)fmt.Println(rj)}Expected nil, but got: json.SyntaxError{msg:invalid character N after object key:value pair, Offset:12}新时代的明星 json v2从 https://pkg.go.dev/encoding/json?tabversions 中可以看到json 包在 go1 也就是最初的版本就已经存在了只是当时有一些设计和特性放到当下来看是有些老旧的由于 Go 的兼容性承诺也不便对其进行大刀阔斧的改动正是因为如此在最近的版本中 go 团队推出了新的 json 包也就是 json/v2 来解决 json 编解码的一些痛点问题。如果对具体内容感兴趣可以去阅读官方的文档 https://pkg.go.dev/encoding/json/v2包括 v1 版本和 v2 版本的一些区别 https://pkg.go.dev/encoding/json#hdr-Migrating_to_v2以及介绍新版本 json 的博客 [https://go.dev/blog/jsonv2-exp](A new experimental Go API for JSON)。会用 v2 实现 v1只是 v1 中原本的一些特性在 v2 中会变成可选择的 Option 提供出来以保证兼容性这些选项不乏上文提到的一些特殊性质譬如编解码结构体时字段大小写敏感 (case-sensitive)omitempty 起作用的对象会发生变化nil 的 slice 和 map 会编码成空数组和空结构体而不是 null以及其他的一些性质

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询