投影是 JMESPath 的主要功能之一。它允许您将表达式应用于元素集合。有五种投影:
- 列表投影
- 切片投影
- 物体投影
- 展平投影
- 过滤器投影
1.列表投影
通配符表达式 创建一个列表投影,这是在一个JSON阵列的投影。
列表中嵌套多个字典,每个元素都是json对象每个元素都是key-value都是一样的,如果想拿到某一个key下的所有value
import jmespath
dict1 = {
"people": [
{"first": "James", "last": "d"},
{"first": "Jacob", "last": "e"},
{"first": "Jayden", "last": "f"},
{"missing": "different"}
],
"foo": {"bar": "baz"}
}
res1 = jmespath.search('people[*]',dict1)
print(res1)# [{'first': 'James', 'last': 'd'}, {'first': 'Jacob', 'last': 'e'}, {'first': 'Jayden', 'last': 'f'}, {'missing': 'different'}]
可以看到people[]中的通配符 匹配了列表中的所有元素,并且以列表的形式返回。那么这时候取key是first的值,只需要people[*].first
import jmespath
dict1 = {
"people": [
{"first": "James", "last": "d"},
{"first": "Jacob", "last": "e"},
{"first": "Jayden", "last": "f"},
{"missing": "different"}
],
"foo": {"bar": "baz"}
}
res2 = jmespath.search('people[*].first',dict1)
print(res2) # ['James', 'Jacob', 'Jayden']
可以看到,查找到的元素同样被放到了通配符创建的列表里,并返回。
使用投影时需要记住一些事项。这些在规范的通配符表达式部分有更详细的讨论,但要点是:
- 投影被评估为两个步骤。左侧 (LHS) 创建初始值的 JSON 数组。投影的右侧 (RHS) 是为左侧创建的 JSON
数组中的每个元素进行投影的表达式。在评估左侧和/或右侧时,每种投影类型的语义略有不同。 - 如果投影到单个数组元素上的表达式的结果为 null,则该值将从收集的结果集中省略。
- 您可以使用管道表达式停止投影。
- 列表投影仅对 JSON 数组有效。如果该值不是列表,则表达式的结果为null。
2.切片投影
切片投影几乎与列表投影相同,不同之处在于左侧是切片的计算结果,它可能不包括原始列表中的所有元素:
import jmespath
dict1 = {
"people": [
{"first": "James", "last": "d"},
{"first": "Jacob", "last": "e"},
{"first": "Jayden", "last": "f"},
{"missing": "different"}
],
"foo": {"bar": "baz"}
}
res3 = jmespath.search('people[:2]',dict1)
print(res3) # [{'first': 'James', 'last': 'd'}, {'first': 'Jacob', 'last': 'e'}]
res4 = jmespath.search('people[:2].first',dict1)
print(res4) # ['James', 'Jacob']
在字典的值里再嵌套列表,依然可以被投影到通配符创建的列表里。
import jmespath
dict2 = {
"people": [
{"first": "James", "last": "d"},
{"first": "Jacob", "last": "e"},
{"first": "Jayden", "last": "f"},
{"first": [["a","b","c"],2,3,4,5], "last": "g"},
{"missing": "different"}
],
"foo": {"bar": "baz"}
}
res5 = jmespath.search('people[*].first[0]',dict2)
print(res5) # [['a', 'b', 'c']]
无效情况,返回空列表[]
import jmespath
dict2 = {
"people": [
{"first": "James", "last": "d"},
{"first": "Jacob", "last": "e"},
{"first": "Jayden", "last": "f"},
{"first": [["a","b","c"],2,3,4,5], "last": "g"},
{"missing": "different"}
],
"foo": {"bar": "baz"}
}
# 下标越界,通配符返回空列表
res6 = jmespath.search('people[*].first[10]',dict2)
print(res6) # []
列表投影仅对list列表有效。如果值不是列表,则表达式的结果为null。
比如,用列表投影取foo[*]就不行了,因为 “foo”: {“bar”: “baz”} 对应的是一个json对象,所以会得到一个null。
import jmespath
dict2 = {
"people": [
{"first": "James", "last": "d"},
{"first": "Jacob", "last": "e"},
{"first": "Jayden", "last": "f"},
{"first": [["a","b","c"],2,3,4,5], "last": "g"},
{"missing": "different"}
],
"foo": {"bar": "baz"}
}
res7 = jmespath.search('foo[*]',dict2)
print(res7) # None
3.对象投影
列表投影是为 JSON 数组定义的,而对象投影是为 JSON 对象定义的。您可以使用* 语法创建对象投影。这将创建 JSON 对象的值列表,并将投影的右侧投影到值列表上。
import jmespath
dict1 = {
"ops": {
"functionA": {"numArgs": 2},
"functionB": {"numArgs": 3},
"functionC": {"variadic": True}
}
}
res1 = jmespath.search('ops.*',dict1)
print(res1) # [{'numArgs': 2}, {'numArgs': 3}, {'variadic': True}]
res2 = jmespath.search('ops.*.numArgs',dict1)
print(res2) # [2, 3]
可以看到,创建了json列表,并且通配符处于哪个层级的key,就会遍历这个层级key的所有value。
上面的例子中, ops.就是会找 functionA、functionB、functionC,而更进一步ops..numArgs 则是继续往里面找到key为numArgs的值,并放到投影的列表里去返回,所以看的返回的结果是[2, 3] 。
官方的解释
ops. .numArgs中的通配符*我们可以看做一个分界线,分为左边和右边,即左边ops,右边numArgs 。
第一步,左边初始化了一个可以投影的数组:
evaluate(ops, inputData) -> [{"numArgs": 2}, {"numArgs": 3},
{"variadic": True}]
第二步,右边遍历数组里的每一个元素:
evaluate(numArgs, {numArgs: 2}) -> 2
evaluate(numArgs, {numArgs: 3}) -> 3
evaluate(numArgs, {variadic: true}) -> null
但是因为variadic这个key与 numArgs不匹配,所以返回的是null。
而对于null,是不会添加到最终返回的结果数组里的,所以最终结果只有[2, 3]
4.展平投影
在 JMESPath 表达式中可以使用多个投影。在列表/对象投影的情况下,在投影中创建投影时保留原始文档的结构。
例如,让我们采用表达式reserved[].instances[].state。这个表达式是说顶级键保留有一个数组作为值。对于这些数组元素中的每一个,投影instance[*].state表达式。在每个列表元素中,都有一个实例键,它本身就是一个值,我们为列表中的每个列表元素创建一个子投影。这是一个例子:
import jmespath
dict1 = {
"reservations": [
{
"instances": [
{"state": "running"},
{"state": "stopped"}
]
},
{
"instances": [
{"state": "terminated"},
{"state": "running"}
]
}
]
}
res1 = jmespath.search('reservations[*].instances[*].state',dict1)
print(res1) # [['running', 'stopped'], ['terminated', 'running']]
可以看到,reservations列表中嵌套了字典,而instances的value,又是一个列表。
这时候,用reservations[* ].instances[*].state
我们可以再来看reservations[* ].instances[* ],其实最外层的[] 就是 reservations[* ]创建的.
而内部的每一个实例instances[*],也会各自再创建出投影列表,所以结果中最外层的[]里包含了2个子元素[]
那如果我就是要[‘running’, ‘stopped’, ‘terminated’, ‘running’]
根据上面说的,如果只要保留最外侧的[],
那么内层的实例就不需要再初始化创建[]了,那么就去掉*试一下。
import jmespath
dict1 = {
"reservations": [
{
"instances": [
{"state": "running"},
{"state": "stopped"}
]
},
{
"instances": [
{"state": "terminated"},
{"state": "running"}
]
}
]
}
res2 = jmespath.search('reservations[*].instances[].state',dict1)
print(res2) # ['running', 'stopped', 'terminated', 'running']
总结一下它的2个特点:
- 它将子列表展平到父列表中(不是递归的,只是一个级别)。
- 它将创建一个投影,右边的任何内容都将投影到新创建的列表上。 您也可以单独使用[]来展平列表
比如下面例子里有个嵌套列表,先来用[*]看一下,原始的列表结构:
import jmespath
li1 = [
[0, 1],
2,
[3],
4,
[5, [6, 7]]
]
req1 = jmespath.search('[*]',li1)
print(req1) # [[0, 1], 2, [3], 4, [5, [6, 7]]]
用[]展开列表试一下:
import jmespath
li1 = [
[0, 1],
2,
[3],
4,
[5, [6, 7]]
]
req2 = jmespath.search('[]',li1)
print(req2) #[0, 1, 2, 3, 4, 5, [6, 7]]
可以看到,列表成功展开,[0, 1, 2, 3, 4, 5, [6, 7]] ,不是递归展开,只是同级,子列表[6, 7] 与列表其他元素同级。
如果再次展开表达式的结果[][],则会得到[0, 1, 2, 3, 4, 5, 6, 7] 的结果
import jmespath
li1 = [
[0, 1],
2,
[3],
4,
[5, [6, 7]]
]
req3 = jmespath.search('[][]',li1)
print(req3) # [0, 1, 2, 3, 4, 5, 6, 7]
5.过滤投影
到目前为止,我们已经看过:
- 列表/切片投影
- 物体投影
- 展平投影
评估投影的 RHS 是一种基本类型的过滤器。如果针对单个元素计算的表达式结果为null,则该元素将从最终结果中排除。
过滤投影允许您在 评估投影的 RHS之前过滤投影的 LHS 。
例如,假设我们有一个机器列表,每个机器都有一个name和一个 state。我们想要所有正在运行的机器的名称。
import jmespath
dict1 = {
"machines": [
{"name": "a", "state": "running"},
{"name": "b", "state": "stopped"},
{"name": "b", "state": "running"}
]
}
# 过滤 取出state为running的name, 过滤表达式为单引号
res1 = jmespath.search("machines[?state=='running'].name",dict1)
如果您只想要具有指定状态的每台机器的整个 JSON 对象,您还可以删除表达式末尾的.name。
import jmespath
dict1 = {
"machines": [
{"name": "a", "state": "running"},
{"name": "b", "state": "stopped"},
{"name": "b", "state": "running"}
]
}
# 取出state为running的每台机器的json对象
res2 = jmespath.search("machines[?state=='running']",dict1)
print(res2)
过滤器表达式是为数组定义的,具有一般形式 LHS [? <表达式> <比较器> <表达式>] RHS
6.比较运算符
支持以下操作:
- ==,测试是否相等
- !=,不等式检验
- <,小于
- <=,小于或等于
- >,大于
- >=,大于或等于
每个操作的行为取决于每个计算表达式的类型。
每个运算符的比较语义根据对应的 JSON 类型定义如下:
6.1相等运算符
对于字符串/数字/真/假/空类型,相等是完全匹配。一个 字符串等于另一个字符串,如果他们有他们的代码点的精确序列。文字值true/false/null仅等于它们自己的文字值。两个JSON对象相等,如果它们具有相同的一组键和值的(给出了两个JSON objeccts X和ÿ,对于每个键值对(I, j)的在X,存在等效的一对(I, j)的在Ý) . 如果两个 JSON 数组具有相同顺序的相等元素(给定两个数组x和,对于从0到length(x) 的每个i, x[i] == y[i])。
6.2排序运算符
排序运算符>, > =, <, <=是仅适用于数字。使用比较运算符评估任何其他类型将产生空 值,这将导致元素从结果列表中排除。例如,给定:
search('foo[?a<b]', {"foo": [{"a": "char", "b": "char"},
{"a": 2, "b": 1},
{"a": 1, "b": 2}]})
foo 列表中的三个元素根据a < b进行评估。第一个元素解析为比较"char" < “bar”,并且由于这些类型是字符串,因此表达式结果为null,因此第一个元素不包含在结果列表中。第二个元素解析为2 < 1,这是false,因此第二个元素从结果列表中排除。第三个表达式解析为1 < 2,其计算结果为true,因此第三个元素包含在列表中。该表达式的最终结果是[{“a”: 1, “b”: 2}]
例如
search(foo[?bar==`10`], {"foo": [{"bar": 1}, {"bar": 10}]}) -> [{"bar": 10}]
search( [?bar==`10`], [{"bar": 1}, {"bar": 10}]}) -> [{"bar": 10}]
search(foo[?a==b] , {"foo": [{"a": 1, "b": 2}, {"a": 2, "b": 2}]}) -> [{"a": 2, "b" :2}]