momo's Blog.

Jsonnet学习

字数统计: 3.1k阅读时长: 15 min
2021/02/01 Share

前言

在学习kube prometheus operator的时候,发现很多项目都用到了 jsonnet 这个开源库, 所以特地来学习一下.

语法

安装

1
2
3
4
5
6
go get github.com/google/go-jsonnet/cmd/jsonnet
或者

# 下载对应的安装包
https://github.com/google/go-jsonnet/releases/tag/v0.17.0
tar zxf go-jsonnet_0.17.0_Linux_x86_64.tar.gz -C /usr/local/bin/

基础语法

  • 有效字符可以不需要加引号

  • 注释可以用#号

    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
    {
    a: 2,
    b: {
    w: 3,
    q: 4 + 1,
    "wda daw": [
    {kind: "23", qty: 1.5}
    ]
    }
    }


    jsonnet test.jsonnet
    {
    "a": 2,
    "b": {
    "q": 5,
    "w": 3,
    "wda daw": [
    {
    "kind": "23",
    "qty": 1.5
    }
    ]
    }
  • 转义引号使用"'momo'", 而不是 '\'momo\''

  • 使用”|||”来包含多行字符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 正确写法
{
a: |||
锄禾日当午,
汗滴禾下土.
谁知盘中餐,
粒粒皆辛苦.
|||,
}

# output
{
"a": "锄禾日当午,\n汗滴禾下土.\n谁知盘中餐,\n粒粒皆辛苦.\n"
}

# 错误写法, ||| 需要单独占用一行才行
{
a: |||
锄禾日当午,
汗滴禾下土.
谁知盘中餐,
粒粒皆辛苦.|||,
}
  • 如果有特殊字符需要转义,可以使用 @'momo' 或者 @"momo"
1
2
3
4
5
6
7
8
9
{
a: @"2 \223@$$&*(,./"
}


# output
{
"a": "2 \\223@$$&*(,./"
}

定义变量

  • 变量使用local定义
  • 如果变量在字段内部,则使用 ','号结尾
  • 如果在其他情况,使用';'号结尾
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 一般变量的定义方式
local name = 'momo';

{
# 在字段内部变量的定义方式
local arg = '18',

person: {
"name": name,
"arg": arg
}
}


# output
{
"person": {
"arg": "18",
"name": "momo"
}
}

引用变量

  • self 引用当前的对象
  • $ 引用最外部对象
  • ['foo'] 查看字段
  • .f 引用字段名称
  • [10] 引用数组的元素
  • [':2'] 可以像python一样进行切片查找
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# `self` 引用当前的对象
{
# 这里无法通过self引用
momo: 1,
person: {
"name": 32,
arg1: 1,
# 只能引用当前对象
"arg2": self.arg1,
name1: self.name,
}
}

# output
{
"momo": 1,
"person": {
"arg1": 1,
"arg2": 1,
"name": 32,
"name1": 32
}
}

# `$` 引用最外部对象

{
# 这里无法通过self引用
momo: 1,
person: {
"name": 32,
arg1: 1,
# 只能引用当前对象
"arg2": self.arg1,
name1: self.name,
momo: $.momo
}
}

# output

{
"momo": 1,
"person": {
"arg1": 1,
"arg2": 1,
"momo": 1,
"name": 32,
"name1": 32
}
}



# .f 引用字段名称
# [10] 引用数组的元素
# [':2'] 可以像python一样进行切片查找

{
// 这里无法通过self引用
momo: 1,
ingredients: [
{ kind: "Farmer's Gin", qty: 1.5 },
{ kind: 'Lemon', qty: 1 },
{ kind: 'Simple Syrup', qty: 0.5 },
{ kind: 'Soda', qty: 2 },
{ kind: 'Angostura', qty: 'dash' },
],
person: {
name: 32,
arg1: 1,
// 只能引用当前对象
arg2: self.arg1,
name1: self.name,
momo: $.momo,
// 引用列表元素
kind: $.ingredients[1].kind,
// 列表分片
kind_list: $.ingredients[:3],
},
}


# output

{
"ingredients": [
{
"kind": "Farmer's Gin",
"qty": 1.5
},
{
"kind": "Lemon",
"qty": 1
},
{
"kind": "Simple Syrup",
"qty": 0.5
},
{
"kind": "Soda",
"qty": 2
},
{
"kind": "Angostura",
"qty": "dash"
}
],
"momo": 1,
"person": {
"arg1": 1,
"arg2": 1,
"kind": "Lemon",
"kind_list": [
{
"kind": "Farmer's Gin",
"qty": 1.5
},
{
"kind": "Lemon",
"qty": 1
},
{
"kind": "Simple Syrup",
"qty": 0.5
}
],
"momo": 1,
"name": 32,
"name1": 32
}
}
  • 为了引用介于最外层和当前对象之间的对象, 可以通过重新声明一个变量的方式
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
31
32
33
34
{
Martini: {
// 这里重新把Martini赋值为变量 drink
local drink = self,
ingredients: [
{ kind: "Farmer's Gin", qty: 1 },
{
kind: 'Dry White Vermouth',
// 这里引用上级对象的值
qty: drink.ingredients[0].qty,
},
],
garnish: 'Olive',
served: 'Straight Up',
},
}

# output
{
"Martini": {
"garnish": "Olive",
"ingredients": [
{
"kind": "Farmer's Gin",
"qty": 1
},
{
"kind": "Dry White Vermouth",
"qty": 1
}
],
"served": "Straight Up"
}
}

算数运算

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
{
// 列表加减
concat_array: [1, 2, 3] + [4],
// 字符串加减
concat_string: '123' + 4,
equality1: 1 == '1',
// 列表比较
equality2: [{}, { x: 3 - 1 }]
== [{}, { x: 2 }],
ex1: 1 + 2 * 3 / (4 + 5),
// Bitwise operations first cast to int.
ex2: self.ex1 | 3,
// Modulo operator.
ex3: self.ex1 % 2,
// 表达式运算
ex4: (4 > 3) && (1 <= 3) || false,
// Mixing objects together
obj: { a: 1, b: 2 } + { b: 3, c: 4 },
// Test if a field is in an object
obj_member: 'foo' in { foo: 1 },
// String formatting
str1: 'The value of self.ex2 is '
+ self.ex2 + '.',
str2: 'The value of self.ex2 is %g.'
% self.ex2,
str3: 'ex1=%0.2f, ex2=%0.2f'
% [self.ex1, self.ex2],
// By passing self, we allow ex1 and ex2 to
// be extracted internally.
str4: 'ex1=%(ex1)0.2f, ex2=%(ex2)0.2f'
% self,
// Do textual templating of entire files:
str5: |||
ex1=%(ex1)0.2f
ex2=%(ex2)0.2f
||| % self,
}



# output

{
"concat_array": [
1,
2,
3,
4
],
"concat_string": "1234",
"equality1": false,
"equality2": true,
"ex1": 1.6666666666666665,
"ex2": 3,
"ex3": 1.6666666666666665,
"ex4": true,
"obj": {
"a": 1,
"b": 3,
"c": 4
},
"obj_member": true,
"str1": "The value of self.ex2 is 3.",
"str2": "The value of self.ex2 is 3.",
"str3": "ex1=1.67, ex2=3.00",
"str4": "ex1=1.67, ex2=3.00",
"str5": "ex1=1.67\nex2=3.00\n"
}

定义函数

标准库参考

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// 定义一个本地函数
local my_function(x, y=10) = x + y;

// 定义一个多行函数
local multiline_function(x) =
// 本地变量.
local temp = x * 2;
// 设置函数内处理结果,并已 ; 号结尾.
[temp, temp + 1];

local object = {
// 增加一个方法
my_method(x): x * x,
};

{
// Functions are first class citizens.
call_inline_function:
(function(x) x * x)(5),

call_multiline_function: multiline_function(4),

// Using the variable fetches the function,
// the parens call the function.
call: my_function(2),

// Like python, parameters can be named at
// call time.
named_params: my_function(x=2),
// This allows changing their order
named_params2: my_function(y=3, x=2),

// object.my_method returns the function,
// which is then called like any other.
// 调用根对象的方法
call_method1: object.my_method(3),

// 使用标准库
standard_lib:
std.join(' ', std.split('foo/bar', '/')),
len: [
std.length('hello'),
std.length([1, 2, 3]),
],
}

# output

{
"call": 12,
"call_inline_function": 25,
"call_method1": 9,
"call_multiline_function": [
8,
9
],
"len": [
5,
3
],
"named_params": 12,
"named_params2": 5,
"standard_lib": "foo bar"
}
  • 定义多行函数的时候, 实际上并没有类似于C++,java的语句块,而是返回值

简单的理解,在jsonnet定义的函数,=等于什么,那他会向下查找非变量的值, 遇到第一个以;或者,结尾的则返回.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// This function returns an object. Although
// the braces look like Java or C++ they do
// not mean a statement block, they are instead
// the value being returned.
// 这里的返回值实际上是一个对象,{}并不是语句块的意思, 可以不加。
local Sour(spirit, garnish='Lemon twist') = {
ingredients: [
{ kind: spirit, qty: 2 },
{ kind: 'Egg white', qty: 1 },
{ kind: 'Lemon Juice', qty: 1 },
{ kind: 'Simple Syrup', qty: 1 },
],
garnish: garnish,
served: 'Straight Up',
};

{
'Whiskey Sour': Sour('Bulleit Bourbon',
'Orange bitters'),
'Pisco Sour': Sour('Machu Pisco',
'Angostura bitters'),
}

条件判断

  • 条件表达式 if a then b else c 如果else没有定义,而a是false,则返回为none
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
local Mojito(virgin=false, large=false) = {
// A local next to fields ends with ','.
local factor = if large then 2 else 1,
// The ingredients are split into 3 arrays,
// the middle one is either length 1 or 0.
ingredients: [
{
kind: 'Mint',
action: 'muddle',
qty: 6 * factor,
unit: 'leaves',
},
] + (
if virgin then [] else [
{ kind: 'Banks', qty: 1.5 * factor },
]
) + [
{ kind: 'Lime', qty: 0.5 * factor },
{ kind: 'Simple Syrup', qty: 0.5 * factor },
{ kind: 'Soda', qty: 3 * factor },
],
// Returns null if not large.
garnish: if large then 'Lime wedge',
served: 'Over crushed ice',
};

{
Mojito: Mojito(),
'Virgin Mojito': Mojito(virgin=true),
'Large Mojito': Mojito(large=true),
}



# output
{
"Large Mojito": {
"garnish": "Lime wedge",
"ingredients": [
{
"action": "muddle",
"kind": "Mint",
"qty": 12,
"unit": "leaves"
},
{
"kind": "Banks",
"qty": 3
},
{
"kind": "Lime",
"qty": 1
},
{
"kind": "Simple Syrup",
"qty": 1
},
{
"kind": "Soda",
"qty": 6
}
],
"served": "Over crushed ice"
},
"Mojito": {
"garnish": null,
"ingredients": [
{
"action": "muddle",
"kind": "Mint",
"qty": 6,
"unit": "leaves"
},
{
"kind": "Banks",
"qty": 1.5
},
{
"kind": "Lime",
"qty": 0.5
},
{
"kind": "Simple Syrup",
"qty": 0.5
},
{
"kind": "Soda",
"qty": 3
}
],
"served": "Over crushed ice"
},
"Virgin Mojito": {
"garnish": null,
"ingredients": [
{
"action": "muddle",
"kind": "Mint",
"qty": 6,
"unit": "leaves"
},
{
"kind": "Lime",
"qty": 0.5
},
{
"kind": "Simple Syrup",
"qty": 0.5
},
{
"kind": "Soda",
"qty": 3
}
],
"served": "Over crushed ice"
}
}
  • 也可以去定义字段名
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
local Margarita(salted) = {
ingredients: [
{ kind: 'Tequila Blanco', qty: 2 },
{ kind: 'Lime', qty: 1 },
{ kind: 'Cointreau', qty: 1 },
],
// 这里新建了这个字段, 如果为false 则不会显示
[if salted then 'garnish']: 'Salt',
};
{
Margarita: Margarita(true),
'Margarita Unsalted': Margarita(false),
}

# output

{
"Margarita": {
"garnish": "Salt",
"ingredients": [
{
"kind": "Tequila Blanco",
"qty": 2
},
{
"kind": "Lime",
"qty": 1
},
{
"kind": "Cointreau",
"qty": 1
}
]
},
"Margarita Unsalted": {
"ingredients": [
{
"kind": "Tequila Blanco",
"qty": 2
},
{
"kind": "Lime",
"qty": 1
},
{
"kind": "Cointreau",
"qty": 1
}
]
}
}

类似于python的列表推导式

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
31
32
local arr = std.range(5, 8);
{
array_comprehensions: {
higher: [x + 3 for x in arr],
lower: [x - 3 for x in arr],
evens: [x for x in arr if x % 2 == 0],
odds: [x for x in arr if x % 2 == 1],
evens_and_odds: [
'%d-%d' % [x, y]
for x in arr
if x % 2 == 0
for y in arr
if y % 2 == 1
],
},
object_comprehensions: {
evens: {
['f' + x]: true
for x in arr
if x % 2 == 0
},
// Use object composition (+) to add in
// static fields:
mixture: {
f: 1,
g: 2,
} + {
[x]: 0
for x in ['a', 'b', 'c']
},
},
}

imports

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
local martinis = import 'martinis.libsonnet';

{
'Vodka Martini': martinis['Vodka Martini'],
Manhattan: {
ingredients: [
{ kind: 'Rye', qty: 2.5 },
{ kind: 'Sweet Red Vermouth', qty: 1 },
{ kind: 'Angostura', qty: 'dash' },
],
// importstr 导入UTF8的文档
garnish: importstr 'garnish.txt',
served: 'Straight Up',
},
}

# martinis.libsonnet
{
'Vodka Martini': {
ingredients: [
{ kind: 'Vodka', qty: 2 },
{ kind: 'Dry White Vermouth', qty: 1 },
],
garnish: 'Olive',
served: 'Straight Up',
},
Cosmopolitan: {
ingredients: [
{ kind: 'Vodka', qty: 2 },
{ kind: 'Triple Sec', qty: 0.5 },
{ kind: 'Cranberry Juice', qty: 0.75 },
{ kind: 'Lime Juice', qty: 0.5 },
],
garnish: 'Orange Peel',
served: 'Straight Up',
},
}

# garnish.txt

Maraschino Cherry


# output

{
"Manhattan": {
"garnish": "Maraschino Cherry",
"ingredients": [
{
"kind": "Rye",
"qty": 2.5
},
{
"kind": "Sweet Red Vermouth",
"qty": 1
},
{
"kind": "Angostura",
"qty": "dash"
}
],
"served": "Straight Up"
},
"Vodka Martini": {
"garnish": "Olive",
"ingredients": [
{
"kind": "Vodka",
"qty": 2
},
{
"kind": "Dry White Vermouth",
"qty": 1
}
],
"served": "Straight Up"
}
}

面向对象

  • 对象 Json Object 就是 Jsonnet 中的对象
  • 对象的组合操作用+号, 会合并两个对象. 如果字段发生冲突,则使用右侧的对象字段覆盖左侧的字段, 类似于 面向对象继承的特性
  • self 是对当前对象的引用(类似于Python)

还有一些jsonnet特有的功能

  • 可以使用 :: 定义隐藏的字段, 这些字段不会出现在生成的Json中
  • 使用super关键字, 可以引用父对象.
  • 使用+: 可以更深层的合并字段
  • : 默认值, 除非隐藏具有相同名称的父级字段, 否则为可见
  • :: 隐藏
  • ::: 强制可见
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
通常,将Jsonnet对象看作一叠layers很有用。图层由字段组成。对象文字或对象理解是单层对象。对象继承A+B创建一个新的对象,该对象的B层位于顶层之上A。

对字段的引用self对应于从堆栈的顶部开始向底部寻找字段,直到找到该字段为止。通过super搜索从当前图层下面的图层开始的字段进行引用。
$ cat>test.jsonnet<<EOF
local base = {
f: 2,
g: self.f + 100,
};
base + {
f: 5,
old_f: super.f,
old_g: super.g,
}
EOF
$ jsonnet test.jsonnet
{
"f": 5,
"g": 105,
"old_f": 2,
"old_g": 105
}

# 有时候我们希望一个对象中的字段在进行组合时不要
# 覆盖父对象中的字段, 而是与相同的字段继续进行组合

# 这时可以用 +: 来声明这个字段 (+:: 与 +: 的含义相同,
# 但与 :: 一样的道理, +:: 定义的字段是隐藏的)

local Base = {
f: 2,
g: self.f + 100,
};

local WrapperBase = {
Base: Base,
};

{
Derived: Base + {
f: 5,
old_f: super.f,
old_g: super.g,
},
WrapperDerived: WrapperBase + {
// 这里不会替换整个Base字段, 而是去组合
Base+: { f: 5, c: 4 },
},
}

# output

{
"Derived": {
"f": 5,
"g": 105,
"old_f": 2,
"old_g": 105
},
"WrapperDerived": {
"Base": {
"c": 4,
"f": 5,
"g": 105
}
}
}



# 对于 JSON Object, 我们更希望进行组合而非覆盖, 因此在定义 Object
# 字段时, 很多库都会选择使用 +: 和 +::, 但我们也要注意不能滥用
$ cat>test.jsonnet<<EOF
local child = {
override: {
x: 1,
},
composite+: {
x: 1,
},
};
{
override: { y: 5, z: 10 },
composite: { y: 5, z: 10 },
} + child
EOF
$ jsonnet test.jsonnet
{
"composite": {
"x": 1,
"y": 5,
"z": 10
},
"override": {
"x": 1
}
}

# 库与 import:
# jsonnet 共享库复用方式其实就是将库里的代码整合到当前文件中来,
# 引用方式也很暴力, 使用 -J 参数指定 lib 文件夹, 再在代码里 import 即可

# jsonnet 约定库文件的后缀名为 .libsonnet
$ mkdir some-path
$ cat>some-path/mylib.libsonnet<<EOF
{
newVPS(ip,
region='cn-hangzhou',
distribution='CentOS 7',
cpu=4,
memory='16GB'):: {
ip: ip,
distribution: distribution,
cpu: cpu,
memory: memory,
vendor: 'Alei Cloud',
os: 'linux',
packages: [],
install(package):: self + {
packages+: [package],
},
}
}
EOF
$ cat>test.jsonnet<<EOF
local vpsTemplate = import 'some-path/mylib.libsonnet';
vpsTemplate
.newVPS(ip='10.10.44.144', cpu=8, memory='32GB')
.install('docker')
.install('jsonnet')
EOF
$ jsonnet -J . test.jsonnet
{
"cpu": 8,
"distribution": "CentOS 7",
"ip": "10.10.44.144",
"memory": "32GB",
"os": "linux",
"packages": [
"docker",
"jsonnet"
],
"vendor": "Alei Cloud"
}

# 上面这种 Builder 模式在 jsonnet 中非常常见,
# 也就是先定义一个构造器, 构造出基础对象然后用各种方法进行修改.
# 当对象非常复杂时, 这种模式比直接覆盖父对象字段更易维护
CATALOG
  1. 1. 前言
  2. 2. 语法
    1. 2.1. 安装
    2. 2.2. 基础语法
    3. 2.3. 定义变量
    4. 2.4. 引用变量
    5. 2.5. 算数运算
    6. 2.6. 定义函数
    7. 2.7. 条件判断
    8. 2.8. 类似于python的列表推导式
    9. 2.9. imports
    10. 2.10. 面向对象