JSON Merge Patch 合并结构体字段数据

作者:matrix 发布时间:2023-10-18 分类:Golang

图片5697-JSON Merge Patch 合并结构体字段数据

json Merge Patch,是一个Internet Engineering Task Force(IETF)标准。基本思想是,你有一个原始的JSON对象,然后根据提供的“补丁”JSON对象,最终生成原始JSON对象需要修改的结果。这种机制适用于部分更新(也称为PATCH更新)的场景。

例子

原始对象:

{
  "Account": "old_account",
  "Name": "old_name",
  "Avatar": "old_avatar"
}

补丁对象(patch object):

{
  "Account": "new_account",
  "Name": null
}

应用补丁对象后的待更新数据(PATCH更新):

{
  "Account": "new_account",
  "Avatar": "old_avatar"
}

简单来说,补丁对象(patch object)描述了以下几种修改:

  • 添加或更新字段:如果补丁中的一个字段在原始对象中不存在,它会被添加;如果存在,它会被更新。

  • 删除字段:如果补丁中的一个字段设置为null,并且该字段在原始对象中存在,那么该字段会被删除。

golang使用

使用实现IETF标准的JSON Merge Patch依赖库 json-patch

go get -u github.com/evanphx/json-patch
// JOSN PATCH
// dst 原始对象
// patch 补丁对象
// return 将补丁应用到原始对象
func MergePatch(dst, patch interface{}) error {
    // 序列化目标(原始)结构体到JSON
    dstJSON, err := json.Marshal(dst)
    if err != nil {
        return err
    }

    // 序列化补丁结构体到JSON,这个补丁描述了如何修改目标(原始)对象
    patchJSON, err := json.Marshal(patch)
    if err != nil {
        return err
    }

    // 使用补丁合并目标(原始)对象
    mergedJSON, err := jsonpatch.MergePatch(dstJSON, patchJSON)
    if err != nil {
        return err
    }

    // 反序列化合并后的JSON回到目标(原始)结构体
    return json.Unmarshal(mergedJSON, dst)
}


调用:


if err := MergePatch(&originJSON, &patchJSON); err != nil { u.JSONResponseError(ctx, err) return } // originJSON 就是应用过补丁的最新原始结构数据

参考:
https://datatracker.ietf.org/doc/html/rfc7396

struct结构体类型2 - 嵌入结构体值 指针类型区别

作者:matrix 发布时间:2023-07-07 分类:Golang

Golang中嵌入结构体类型有两种:值或指针

结论

创建 含内嵌指针struct实例时,必须手动声明嵌入的结构指针。

伪代码如下:

package main
type BaseDao struct{name string}

# 匿名结构体字段BaseDao 
type OptDao1 struct{BaseDao} # 嵌入值
type OptDao2 struct{*BaseDao} # 嵌入指针

func main(){
  opt := OptDao2{BaseDao:&BaseDao{}} //必须手动声明嵌入的结构指针
}

上面代码中OptDao1、OptDao2嵌入了BaseDao结构体,主要区别只有嵌入值的类型不同。值和指针区别

嵌入值

创建OptDao1对象

mOptDao1 := OptDao1{}
mOptDao1.name 

代码调用会正常,属性name会获取到空字符串

其他例:

type Base struct {
    value int
}

func (b *Base) Increase() {
    b.value++
}

type Derived struct {
    Base
}

func main() {
    d := Derived{}
    d.Increase()
    fmt.Println(d.value) // 输出 1
}

嵌入指针

创建OptDao2对象

mOptDao2 := OptDao2{}
mOptDao2.name 

上面代码调用会出现nil空指针异常,runtime error: invalid memory address or nil pointer dereference,即nil指针解引用错误。

原因是访问一个nil对象的方法或属性,这就会panic。

怎么办?

mOptDao2 := OptDao2{BaseDao:&BaseDao{}}
mOptDao2.name 

创建mOptDao2实例时必须声明嵌入的结构指针

其他例:

type Base struct {
    value int
}

func (b *Base) Increase() {
    b.value++
}

type Derived struct {
    *Base
}

func main() {
    d := Derived{Base: &Base{}}
    d.Increase()
    fmt.Println(d.value) // 输出 1
}

简而言之,选择值类型嵌入还是指针类型嵌入,主要取决于你是否需要多个实例共享同一个嵌入实例的状态。如果你需要共享状态,使用指针类型嵌入。如果你不需要共享状态,使用值类型嵌入。

一般情况下选择嵌入值即可,除非多个对象需要共享一个Base结构实例。

Golang的结构体类型struct

作者:matrix 发布时间:2023-06-24 分类:Golang

熟悉面向对象语言的话,Golangstruct结构体有点像面向对象编程中的。但这两者不是完全一样,只能说都有继承、封装、多态的特点。

结构体(struct)

结构体可以将零个或多个任意类型的值聚合在一起,能描述多个数据类型

type Person struct {
  name    string
  age     int
  value   string
  address string
}

成员方法和接收者

func (p *Person) setName(name string) {
  p.name = name
}

说明:
setName 为声明的方法
p *Person为接收者(指针类型)

struc类型新增成员方法的语法很另类,像是单独给struct做绑定,绑定的时候会有接收者来指定当前实例类型。

GOlang其实可以给任何类型创建成员方法:

type MyInt int
func (i MyInt) IsZero() bool{
  return i == 0
}

这里通过声明int的自定义类型MyInt,然后绑定一个成员方法。灵活~

值接收者 指针接收者

方法的接收者可以是结构体的值或者指针。上面例子的接收者是一个Person类型的指针。指针接收者的一个优点是可以直接修改接收者的字段值,还避免值的拷贝(内部实际上是拷贝的指针)。

声明为值接收者也是可以:

func (p Person) getName() string {
  return p.name
}

getName方法会在调用时复制接收者,就可能会导致性能问题。一般是建议使用指针作为接收者

小结

值接收者或者指针接收者 都能调用结构体或者内嵌结构体的方法或者属性。
给结构体绑定成员方法时,参数最好使用指针,防止值拷贝

func (this *Person) setName(name string) 


// 申明结构体struct
type Person struct {
  name    string
  age     int
  value   string
  address string
}

//结构绑定

// 给结构体绑定成员方法。 不推荐使用,内存利用低效(参数会使用值传递,会内存拷贝)
//p Person这里p表示值接收者
// func (p Person) getName() string {
// 这里的p变量指针和外部调用的mPerson不同,这里属于值拷贝!!!
//  return p.name
// }

// 给Person结构体指针绑定成员方法,同上面效果。但是参数属于引用传递
//this为指针接收者
func (this *Person) getName() string {
  return this.name
}

// 给Person结构体指针绑定方法
func (this *Person) setName(name string) {
  this.name = name
}

func main() {
  //创建实例
  // var mPerson *Person = new(Person)//返回实例指针
  // var mPerson Person = *new(Person)//返回实例
  // var mPerson Person = Person{}//返回实例
  var mPerson *Person = &Person{name: "Hi~"} //获取实例指针
  mPerson.setName("")
  fmt.Println(mPerson.getName())
}

go build 交叉编译

作者:matrix 发布时间:2023-02-10 分类:Golang

Golang支持跨平台编译,比起python只能编译为当前平台的二进制文件要好太多。

go build help

$ go help  build
usage: go build [-o output] [build flags] [packages]
...

默认编译(输出当前平台二进制文件)

$ go build -ldflags="-s -w"  main.go && upx -9 ./main

说明:
upx 该工具用于压缩二进制文件 -9 表示最高压缩率(选择范围为1-9)

跨平台编译

Mac/Linux系统下:

输出Linux可执行文件:

$ CGO_ENABLE=0 GOOS=linux GOARCH=amd64 go build -ldflags='-s -w -extldflags "-static -fpic"' -o main-linux-amd64  main.go  && upx -9 ./main-linux-amd64

输出Windows可执行文件:

$ CGO_ENABLE=0 GOOS=windows GOARCH=amd64 go build -ldflags='-s -w -extldflags "-static -fpic"' -o main-windows-amd64.exe  main.go  && upx -9 ./main-windows-amd64.exe

输出Mac可执行文件:

# Intel
$ CGO_ENABLE=0 GOOS=darwin GOARCH=amd64 go build -ldflags='-s -w -extldflags "-static -fpic"' -o main-darwin-amd64  main.go  && upx -9 ./main-darwin-amd64

# Apple Silicon (M1)
$ CGO_ENABLE=0 GOOS=darwin GOARCH=arm64 go build -ldflags='-s -w -extldflags "-static -fpic"' -o main-darwin-arm64 main.go  && upx -9 ./main-darwin-arm64

windows系统下:

SET CGO_ENABLE=0
SET GOOS=linux 
SET GOARCH=amd64
go build -ldflags='-s -w -extldflags "-static -fpic"'  main.go

参数说明

CGO_ENABLE 是否开启CGO。默认为1开启CGO(不支持交叉编译),0表示关闭CGO
GOARCH 表示CPU架构amd64(x86-64 普通家用电脑)、ARM
GOOS 表示操作系统Linux、Windows、Darwin(Mac)
-o 表示输出指定文件名(默认为main)
-a 表示强制重新构建

-ldflags参数
`-s -w` 表示减小体积,去除编译携带的符号表和调试信息
-s 忽略符号表和调试信息
-w 忽略DWARFv3调试信息,将无法使用gdb进行调试

-extldflags "-static -fpic" 表示完全静态编译(默认是静态链接 个别库可能会导致动态链接),这样编译生成的文件就可以任意放到指定平台下运行,不需要运行环境的基础依赖库()

二进制压缩工具upx(可选)

Mac下使用brew安装

$ brew install upx
$ upx --version 
upx 3.96

其他平台:
https://github.com/upx/upx/releases

upx压缩工具能省掉40%-70%的大小

-rwxr-xr-x   1 hhtjim  staff   7.9M Feb  9 00:13 main-default
-rwxr-xr-x   1 hhtjim  staff   5.5M Feb  9 00:15 main-ldflags
-rwxr-xr-x   1 hhtjim  staff   2.3M Feb  9 00:34 main-upx

参考:

https://mp.weixin.qq.com/s/hx9Vbdes6cmRNMiUNqOTCQ

https://mp.weixin.qq.com/s/s6QzFkT1YU3lwkuZSHNb-A

https://dev.to/tidalcloud/how-to-cross-compile-go-app-for-apple-silicon-m1-27l6

https://docs.studygolang.com/pkg/runtime/internal/sys/#pkg-constants

https://zhuanlan.zhihu.com/p/338891206

完全静态编译一个Go程序
https://colobu.com/2018/07/20/totally-static-Go-builds/

Go编译常用参数
https://juejin.cn/post/7096772208999006244

golang的变量声明

作者:matrix 发布时间:2023-01-31 分类:Golang

变量声明

Golang属于强类型语言,且定义的变量一定要被使用不然会编译报错。

Golang可以使用:=的语法糖来自动实现类型推断,一般都在非全局变量中使用。var的声明多用在全局变量声明。

变量赋值后必须使用,否则编译失败

例外: _变量 表示占位变量。不能赋值和调用,仅为占位作用。

var a int = 16
var a = 16 //类型自动推断
//等同于短变量声明
a := 16
//多变量快捷声明
var a, b int
var a, b, c = 16, true, "str" //可以不同类型

变量作用域

  {
    var b string = "hi~"
    fmt.Println("asd")
    fmt.Println("b->", b)
  }

  num := 12
  {
    a, num := false, 5 // 这里的num会被认为是一个新变量
    b := 100
    fmt.Println(a, num, b)
  }

  fmt.Println(a, num) // a: undefined

{ ... }代码块会限制变量作用域

变量默认值

基本数据类型默认值都是 0、空字符串这些,声明时就划分内存空间

//声明即初始化(划分内存空间)

var a int //0
//等同于 var a int = 0

var a string //""
var a bool //false
var a rune //0
var a byte //0
var a float32 //0
...


其他类型都默认为nil值
var a error 
// var a error = nil
var a *int
var a []int
var a map[string]string
...

参考:
https://mp.weixin.qq.com/s/m8iQdGu7g_8PX4s9mFbkDQ