go语言巧用intstr来处理数字与字符类型

intstr 这个包是偶然在k8s client-go中发现的, 在编写deployment结构体定义时, apiv1.Container实例下LivenessProbe中用到的Port, 就是intstr.IntOrString. 特地看下来这个函数, 发现这个函数恰巧解决了一直困扰我的一个问题: 在restful接口中, 如果要求json格式body体内传递port端口参数, 那么port这个字段, 我应该在服务端定义成整型好呢, 还是字符串类型好呢, 一直很纠结~ 有了intstr.IntOrString再也不用为这事儿发愁了

简介

以往我们接收jsonyaml字符串要转成结构体时, 数据类型必须一一对应. 比如数据中的端口字段, 如果json 中传递是的整型, 则为以下样式 {"port": 8080}; 如果传递的是字符串类型, 则为以下形式{"port": "8080"}

服务端接收到这些字符串后, 需要将数据与结构体中的字段一一绑定, 当然数据类型也必须一致. 如果在服务端定义了port字段为int, 但是json字符串传递过来的是{"port": "8080"}, 则绑定数据时将会报错. 反之也一样, 数据类型必须一致.

我自己开发过得项目中, 很多接口的body体内都出现了端口字段, 有的时候定义为整型, 有的时候定义为字符串类型, api doc里也是有的写得整型, 有的写的字符串, 每次都纠结用什么类型好

intstr.IntOrString 数据类型顾名思义, 该类型可以既可以接受整型, 也可以接受字符串类型. 再也不用纠结这种用整型和字符串类型都合适的字段到底用什么数据类型了

intstr.IntOrString 不仅可以兼容接收整型或字符串类型, 而且可以通过其内置的函数, 在两个数据类型之间自由转换

json/yaml串转结构体

以下demo的场景为, 拿到json/yaml串, 转换成结构体的使用场景

端口字段为整型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import (
"encoding/json"
"fmt"
"k8s.io/apimachinery/pkg/util/intstr"
)

type service struct {
Name string `json:"name"`
Host string `json:"host"`
Port intstr.IntOrString `json:"port"`
}

func main() {

// 定义json字符串, port字段的值设置为整型
jsonStr := `{"name": "nginx", "host": "192.168.1.2", "port": 8080}`

nginxService := &service{}

// 将json字符串的数据, 绑定到结构体实例中
json.Unmarshal([]byte(jsonStr), nginxService)

fmt.Printf("提取数据类型: %T 实际数据类型(编号): %v 值: %#v \n", nginxService.Port.IntVal, nginxService.Port.Type, nginxService.Port.IntVal)
fmt.Printf("提取数据类型: %T 实际数据类型(编号): %v 值: %#v \n", nginxService.Port.StrVal, nginxService.Port.Type, nginxService.Port.StrVal)
}

运行结果:

1
2
提取数据类型: int32 实际数据类型(编号): 0 值: 8080 
提取数据类型: string 实际数据类型(编号): 0 值: ""

上面的例子中, json字符串中的端口字段为整型, 绑定到结构体的实例后, 通过nginxService.Port.Type 可以取得json串中实际传递的是哪种数据类型, 在本例中, 调用nginxService.Port.Type的结果是0, 通过查看源码得知, 0表示json串中传递的数据是整型

1
2
3
4
5
6
7
8
// 源码定义
// Type represents the stored type of IntOrString.
type Type int

const (
Int Type = iota // The IntOrString holds an int.
String // The IntOrString holds a string.
)

在实际使用时, 可以先判断nginxService.Port.Type的结果是0, 还是1

  • 如果是0, 则取整型nginxService.Port.IntVal
  • 如果是1, 则取字符串类型nginxService.Port.StrVal

还有一种更直接的用法, 就是根据需要, 显式的类型转换

比如上面的例子中, json传递的端口值为整型, 但是服务端绑定到结构体之后, 当使用该端口时, 需要该端口值得数据类型为字符串类型, 你可以直接这样做:

fmt.Printf("%#v", nginxService.Port.String())

运行结果:

"8080"

可以看到, 整型的数据, 已经帮你转换成为字符串类型, 后期使用非常灵活, 可以随时提取两种数据类型的值

端口为字符串类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import (
"encoding/json"
"fmt"
"k8s.io/apimachinery/pkg/util/intstr"
)

type service struct {
Name string `json:"name"`
Host string `json:"host"`
Port intstr.IntOrString `json:"port"`
}

func main() {

// 定义json字符串, port字段的值设置为字符串类型
jsonStr := `{"name": "nginx", "host": "192.168.1.2", "port": "8080""}`

nginxService := &service{}

// 将json字符串的数据, 绑定到结构体实例中
json.Unmarshal([]byte(jsonStr), nginxService)

fmt.Printf("提取数据类型: %T 实际数据类型(编号): %v 值: %#v \n", nginxService.Port.IntVal, nginxService.Port.Type, nginxService.Port.IntVal)
fmt.Printf("提取数据类型: %T 实际数据类型(编号): %v 值: %#v \n", nginxService.Port.StrVal, nginxService.Port.Type, nginxService.Port.StrVal)
fmt.Printf("值: %#v 实际数据类型: %T \t \n", nginxService.Port.String(), nginxService.Port.String())
fmt.Printf("值: %#v 实际数据类型: %T \t \n", nginxService.Port.String(), nginxService.Port.IntValue())
}

运行结果:

1
2
3
4
提取数据类型: int32 实际数据类型(编号): 1 值: 0 
提取数据类型: string 实际数据类型(编号): 1 值: "8080"
值: "8080" 实际数据类型: string
值: "8080" 实际数据类型: int

结构体转json/yaml

数据类型: 整型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
"encoding/json"
"fmt"
"k8s.io/apimachinery/pkg/util/intstr"
)

type service struct {
Name string `json:"name"`
Host string `json:"host"`
Port intstr.IntOrString `json:"port"`
}

func main() {

nginxService := service{
Name: "nginx",
Host: "192.168.1.2",
Port: intstr.IntOrString{
Type: 0, // 这里设置为0, 意为数据类型为整型
IntVal: 2222,
StrVal: "1111",
},
}

jsonBytes, _ := json.Marshal(nginxService)
fmt.Println(string(jsonBytes))

}

注意上面intstr.IntOrString.Type的值设置为0, 运行结果:

1
{"name":"nginx","host":"192.168.1.2","port":2222}

intstr.IntOrString.Type的值设置为0, 表示该结构体实例内的数据为整型, 故转json时, 按照IntVal中的值来生成字符串

注意: 如果没有显式为intstr.IntOrString.Type赋值, 则该值默认为0, 默认会取IntVal中的数据, 也就是说, 可以简写为以下形式

1
2
3
4
5
nginxService := service{
Name: "nginx",
Host: "192.168.1.2",
Port: intstr.IntOrString{IntVal: 2222},
}

数据类型: 字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
"encoding/json"
"fmt"
"k8s.io/apimachinery/pkg/util/intstr"
)

type service struct {
Name string `json:"name"`
Host string `json:"host"`
Port intstr.IntOrString `json:"port"`
}

func main() {

nginxService := service{
Name: "nginx",
Host: "192.168.1.2",
Port: intstr.IntOrString{
Type: 1, // 这里设置为1, 意为数据类型为str
IntVal: 2222,
StrVal: "1111",
},
}

jsonBytes, _ := json.Marshal(nginxService)
fmt.Println(string(jsonBytes))

}

注意上面intstr.IntOrString.Type的值设置为1, 运行结果:

1
{"name":"nginx","host":"192.168.1.2","port":"1111"}

intstr.IntOrString.Type的值设置为1, 表示该结构体实例内的数据为字符串类型, 故转json时, 按照StrVal中的值来生成字符串