Loading

JMESPath由浅入深完全入门教程(自用)

JMESPath 完全学习指南 v3.0

目录


1. 简介与安装

什么是JMESPath?

JMESPath(JSON Matching Expression Path)是一种查询语言,用于从JSON文档中提取和转换数据。类似于XPath用于XML,JMESPath专门为JSON设计。

重要说明

本教程基于标准JMESPath规范,严格按照循序渐进的方式介绍每个概念。每个新语法或符号都会在使用前进行完整介绍。

标准功能标记: 🔵 标准JMESPath功能
扩展功能标记: 🟡 部分实现支持的扩展功能
限制说明标记: ⚠️ 功能限制或注意事项

安装

pip install jmespath

Python基础用法

import jmespath

data = {
    "name": "John",
    "age": 30,
    "city": "New York"
}

# 基础查询
result = jmespath.search('name', data)
print(result)  # "John"

第一部分:基础语法

2. 标识符和属性访问 🔵

2.1 基本标识符

语法说明:

  • 直接使用属性名访问对象的属性
  • 标识符可以包含字母、数字、下划线(不能以数字开头)
  • 大小写敏感

示例 1: 简单属性访问

{
  "name": "Alice",
  "age": 25,
  "email": "alice@example.com"
}

查询表达式:

  • name"Alice"
  • age25
  • email"alice@example.com"

2.2 嵌套访问

语法说明:

  • 使用点号.连接多个标识符访问嵌套属性
  • 格式:parent.child.grandchild

示例 1: 两层嵌套

{
  "user": {
    "name": "Bob",
    "profile": {
      "bio": "Developer",
      "location": "Seattle"
    }
  }
}

查询表达式:

  • user.name"Bob"
  • user.profile.bio"Developer"
  • user.profile.location"Seattle"

2.3 特殊字符处理

语法说明:

  • 包含特殊字符的键需要用双引号包围
  • 特殊字符包括:-.@$、空格、数字开头等

示例 1: 特殊字符键名

{
  "first-name": "John",
  "last.name": "Doe",
  "123abc": "starts with number",
  "user name": "Alice Bob"
}

查询表达式:

  • "first-name""John"
  • "last.name""Doe"
  • "123abc""starts with number"
  • "user name""Alice Bob"

2.4 空值处理

语法说明:

  • 访问不存在的属性返回null
  • 路径中任何部分为null时,整个表达式返回null

示例 1: 空值处理

{
  "user": {
    "name": "John"
  }
}

查询表达式:

  • user.agenull (属性不存在)
  • user.profile.bionull (中间路径不存在)

3. 数组索引 🔵

3.1 基本索引

语法说明:

  • 使用方括号[index]访问数组元素
  • 正索引从0开始
  • 负索引从末尾开始,-1是最后一个元素
  • 超出范围返回null

示例 1: 数组索引访问

{
  "fruits": ["apple", "banana", "cherry", "date"]
}

查询表达式:

  • fruits[0]"apple"
  • fruits[1]"banana"
  • fruits[-1]"date"
  • fruits[-2]"cherry"
  • fruits[10]null

3.2 嵌套数组索引

语法说明:

  • 可以连续使用索引访问多维数组
  • 格式:array[i][j]

示例 1: 二维数组

{
  "matrix": [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
  ]
}

查询表达式:

  • matrix[0][0]1
  • matrix[1][2]6
  • matrix[-1][-1]9

4. 切片操作 🔵

4.1 基本切片

语法说明:

  • 格式:[start:stop:step]
  • start: 起始索引(包含)
  • stop: 结束索引(不包含)
  • step: 步长
  • 任何部分都可以省略

示例 1: 切片操作

{
  "numbers": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
}

查询表达式:

  • numbers[0:3][0, 1, 2]
  • numbers[2:5][2, 3, 4]
  • numbers[::2][0, 2, 4, 6, 8]
  • numbers[:][0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

4.2 负索引切片

示例 1: 使用负索引

{
  "letters": ["a", "b", "c", "d", "e", "f"]
}

查询表达式:

  • letters[-3:]["d", "e", "f"]
  • letters[:-2]["a", "b", "c", "d"]

4.3 反向切片

语法说明:

  • 使用负步长实现反向

示例 1: 反向操作

{
  "items": [1, 2, 3, 4, 5]
}

查询表达式:

  • items[::-1][5, 4, 3, 2, 1]
  • items[::-2][5, 3, 1]

5. 投影 🔵

5.1 列表投影 [*]

语法说明:

  • [*]符号对数组中的每个元素应用后续表达式
  • 返回结果数组

示例 1: 列表投影

{
  "users": [
    {"name": "Alice", "age": 25},
    {"name": "Bob", "age": 30},
    {"name": "Charlie", "age": 35}
  ]
}

查询表达式:

  • users[*].name["Alice", "Bob", "Charlie"]
  • users[*].age[25, 30, 35]

5.2 对象投影 *

语法说明:

  • *符号对对象的所有值应用后续表达式
  • 返回值的数组

示例 1: 对象投影

{
  "departments": {
    "sales": {"count": 10, "budget": 50000},
    "engineering": {"count": 25, "budget": 100000},
    "hr": {"count": 5, "budget": 30000}
  }
}

查询表达式:

  • departments.*.count[10, 25, 5]
  • departments.*.budget[50000, 100000, 30000]

5.3 扁平化投影 []

语法说明:

  • []符号扁平化嵌套数组
  • 将多层数组展平为单层

示例 1: 扁平化

{
  "groups": [
    {"members": ["Alice", "Bob"]},
    {"members": ["Charlie", "David"]}
  ]
}

查询表达式:

  • groups[*].members[]["Alice", "Bob", "Charlie", "David"]

5.4 切片投影

语法说明:

  • 切片结果可以继续应用投影

示例 1: 切片后投影

{
  "items": [
    {"id": 1, "value": 10},
    {"id": 2, "value": 20},
    {"id": 3, "value": 30}
  ]
}

查询表达式:

  • items[0:2].value[10, 20]

6. 多选 🔵

6.1 多选列表

语法说明:

  • 使用[expression1, expression2, ...]创建新数组
  • 每个表达式的结果成为数组元素

示例 1: 多选列表

{
  "person": {
    "name": "John",
    "age": 30,
    "city": "New York"
  }
}

查询表达式:

  • person.[name, age]["John", 30]
  • person.[name, city]["John", "New York"]

6.2 多选哈希

语法说明:

  • 使用{key1: expression1, key2: expression2}创建新对象

示例 1: 多选哈希

{
  "user": {
    "firstName": "Jane",
    "lastName": "Doe",
    "age": 28
  }
}

查询表达式:

  • user.{name: firstName, years: age}{"name": "Jane", "years": 28}

第二部分:高级特性

7. 特殊符号 🔵

7.1 当前节点 @

语法说明:

  • @符号代表当前处理的节点
  • 在管道和过滤表达式中使用

示例 1: 在过滤中使用@

{
  "numbers": [10, 20, 30, 40, 50]
}

查询表达式:

  • numbers[?@ > \25`][30, 40, 50]`

⚠️ 注意: 这里的反引号`用于创建字面量(将在后面详细介绍)

7.2 表达式引用 &

语法说明:

  • &符号创建表达式引用
  • 主要用于排序和比较函数

示例 1: 表达式引用

{
  "data": [
    {"x": 3, "y": 2},
    {"x": 1, "y": 4}
  ]
}

此符号将在函数章节中详细使用。

7.3 字面量 `

语法说明:

  • 反引号`创建字面量值
  • 用于在表达式中表示常量

示例 1: 字面量使用

{
  "values": [1, 2, 3, 4, 5]
}

查询表达式:

  • values[?@ > \3`][4, 5]`
  • values[?@ == \1`][1]`

8. 管道表达式 | 🔵

8.1 基本管道

语法说明:

  • 使用|将前一个表达式的结果作为后一个表达式的输入
  • 格式:expression1 | expression2

示例 1: 简单管道

{
  "data": {
    "users": [
      {"name": "Alice"},
      {"name": "Bob"},
      {"name": "Charlie"}
    ]
  }
}

查询表达式:

  • data.users | [0]{"name": "Alice"}
  • data.users | [0].name"Alice"

8.2 管道与投影组合

示例 1: 管道后投影

{
  "wrapper": {
    "items": [1, 2, 3, 4, 5]
  }
}

查询表达式:

  • wrapper.items | [0:3][1, 2, 3]

9. 过滤表达式 🔵

9.1 基本过滤

语法说明:

  • 使用[?expression]过滤数组
  • expression必须是布尔表达式
  • 返回满足条件的元素

示例 1: 布尔属性过滤

{
  "users": [
    {"name": "Alice", "active": true},
    {"name": "Bob", "active": false},
    {"name": "Charlie", "active": true}
  ]
}

查询表达式:

  • users[?active] → 返回active为true的用户
  • users[?!active] → 返回active为false的用户

9.2 比较运算符

语法说明:

  • == 等于
  • != 不等于
  • < 小于
  • <= 小于等于
  • > 大于
  • >= 大于等于

示例 1: 数值比较

{
  "products": [
    {"name": "Laptop", "price": 1200},
    {"name": "Mouse", "price": 25},
    {"name": "Keyboard", "price": 75}
  ]
}

查询表达式:

  • products[?price < \100`]` → 价格小于100的产品
  • products[?price >= \75`]` → 价格大于等于75的产品

9.3 字符串比较

示例 1: 字符串相等

{
  "items": [
    {"type": "book", "title": "Python Guide"},
    {"type": "video", "title": "JS Tutorial"},
    {"type": "book", "title": "Java Basics"}
  ]
}

查询表达式:

  • items[?type == 'book'] → 所有书籍

9.4 逻辑运算符

语法说明:

  • && 逻辑与
  • || 逻辑或
  • ! 逻辑非

示例 1: 组合条件

{
  "products": [
    {"name": "A", "price": 50, "inStock": true},
    {"name": "B", "price": 150, "inStock": false},
    {"name": "C", "price": 75, "inStock": true}
  ]
}

查询表达式:

  • products[?price < \100` && inStock]` → 价格低于100且有货
  • products[?price > \100` || !inStock]` → 价格高于100或无货

9.5 使用当前节点过滤

示例 1: 简单数组过滤

{
  "numbers": [5, 10, 15, 20, 25, 30]
}

查询表达式:

  • numbers[?@ > \15`][20, 25, 30]`
  • numbers[?@ <= \20`][5, 10, 15, 20]`

10. 函数 🔵

10.1 类型和长度函数

type() 🔵

说明: 返回值的类型

{"value": 42, "text": "hello"}
  • type(value)"number"
  • type(text)"string"

length() 🔵

说明: 返回数组、对象或字符串的长度

{"items": [1, 2, 3], "text": "Hello"}
  • length(items)3
  • length(text)5

10.2 数组函数

sort() 🔵

说明: 排序数组

{"numbers": [3, 1, 4, 1, 5]}
  • sort(numbers)[1, 1, 3, 4, 5]

reverse() 🔵

说明: 反转数组

{"items": [1, 2, 3]}
  • reverse(items)[3, 2, 1]

min() / max() 🔵

说明: 最小/最大值

{"scores": [85, 92, 78, 95]}
  • min(scores)78
  • max(scores)95

sum() 🔵

说明: 求和

{"values": [10, 20, 30]}
  • sum(values)60

avg() 🔵

说明: 平均值

{"grades": [80, 90, 100]}
  • avg(grades)90

10.3 对象函数

keys() 🔵

说明: 获取对象的键

{"user": {"name": "John", "age": 30}}
  • keys(user)["name", "age"]

values() 🔵

说明: 获取对象的值

{"user": {"name": "John", "age": 30}}
  • values(user)["John", 30]

merge() 🔵

说明: 合并对象

{"obj1": {"a": 1}, "obj2": {"b": 2}}
  • merge(obj1, obj2){"a": 1, "b": 2}

10.4 字符串函数

contains() 🔵

说明: 检查包含关系

{"text": "Hello World", "tags": ["a", "b"]}
  • contains(text, 'World')true
  • contains(tags, 'a')true

starts_with() / ends_with() 🔵

说明: 检查前缀/后缀

{"filename": "document.pdf"}
  • starts_with(filename, 'doc')true
  • ends_with(filename, '.pdf')true

join() 🔵

说明: 连接数组为字符串

{"words": ["Hello", "World"]}
  • join(' ', words)"Hello World"

10.5 转换函数

to_string() / to_number() 🔵

说明: 类型转换

{"num": 42, "str": "123"}
  • to_string(num)"42"
  • to_number(str)123

to_array() 🔵

说明: 转换为数组

{"value": "single"}
  • to_array(value)["single"]

not_null() 🔵

说明: 返回第一个非null值

{"a": null, "b": "value"}
  • not_null(a, b)"value"

10.6 高级函数(使用表达式引用)

sort_by() 🔵

说明: 按表达式排序
语法: sort_by(array, &expression)

{
  "users": [
    {"name": "Charlie", "age": 35},
    {"name": "Alice", "age": 25},
    {"name": "Bob", "age": 30}
  ]
}
  • sort_by(users, &age) → 按年龄排序的数组
  • sort_by(users, &name) → 按名字排序的数组

min_by() / max_by() 🔵

说明: 按表达式获取最小/最大元素

{
  "products": [
    {"name": "A", "price": 100},
    {"name": "B", "price": 50},
    {"name": "C", "price": 150}
  ]
}
  • min_by(products, &price){"name": "B", "price": 50}
  • max_by(products, &price){"name": "C", "price": 150}

11. 组合使用:管道与函数

现在我们已经学习了所有基础概念,可以将它们组合使用。

11.1 管道与函数

示例 1: 管道传递给函数

{
  "data": {
    "numbers": [3, 1, 4, 1, 5, 9, 2, 6]
  }
}

查询表达式:

  • data.numbers | sort(@)[1, 1, 2, 3, 4, 5, 6, 9]
  • data.numbers | max(@)9
  • data.numbers | sum(@)31

11.2 投影后使用函数

示例 1: 提取后聚合

{
  "orders": [
    {"id": 1, "amount": 100},
    {"id": 2, "amount": 200},
    {"id": 3, "amount": 150}
  ]
}

查询表达式:

  • orders[*].amount | sum(@)450
  • orders[*].amount | avg(@)150

11.3 过滤后使用函数

示例 1: 条件聚合

{
  "sales": [
    {"product": "A", "amount": 100, "region": "North"},
    {"product": "B", "amount": 200, "region": "South"},
    {"product": "C", "amount": 150, "region": "North"}
  ]
}

查询表达式:

  • sales[?region == 'North'].amount | sum(@)250

12. 综合练习

12.1 数据转换练习

原始数据:

{
  "company": {
    "employees": [
      {"name": "Alice", "dept": "Engineering", "salary": 75000, "active": true},
      {"name": "Bob", "dept": "Sales", "salary": 60000, "active": true},
      {"name": "Charlie", "dept": "Engineering", "salary": 85000, "active": false},
      {"name": "David", "dept": "HR", "salary": 55000, "active": true}
    ]
  }
}

练习查询:

  1. 获取所有员工姓名:

    • company.employees[*].name
    • 结果:["Alice", "Bob", "Charlie", "David"]
  2. 获取活跃员工:

    • company.employees[?active]
  3. 获取工程部门员工的薪资:

    • company.employees[?dept == 'Engineering'].salary
    • 结果:[75000, 85000]
  4. 计算活跃员工的平均薪资:

    • company.employees[?active].salary | avg(@)
    • 结果:63333.33
  5. 获取薪资最高的员工:

    • max_by(company.employees, &salary)
  6. 创建员工摘要:

    • company.employees[*].{name: name, department: dept}

12.2 复杂数据处理

数据结构:

{
  "store": {
    "inventory": [
      {
        "id": "P001",
        "name": "Laptop",
        "category": "Electronics",
        "price": 999,
        "stock": 5,
        "tags": ["computer", "portable"]
      },
      {
        "id": "P002",
        "name": "Mouse",
        "category": "Electronics",
        "price": 25,
        "stock": 50,
        "tags": ["computer", "accessory"]
      },
      {
        "id": "P003",
        "name": "Notebook",
        "category": "Stationery",
        "price": 5,
        "stock": 200,
        "tags": ["paper", "writing"]
      },
      {
        "id": "P004",
        "name": "Monitor",
        "category": "Electronics",
        "price": 299,
        "stock": 0,
        "tags": ["computer", "display"]
      }
    ]
  }
}

复杂查询示例:

  1. 获取所有电子产品的名称和价格:

    store.inventory[?category == 'Electronics'].{name: name, price: price}
    
  2. 找出缺货的产品:

    store.inventory[?stock == `0`].name
    
  3. 计算所有产品的总库存价值:

    store.inventory[*].[price * stock] | sum(@)
    

    ⚠️ 注意:标准JMESPath不支持算术运算,这需要扩展支持

  4. 获取包含"computer"标签的产品:

    store.inventory[?contains(tags, 'computer')].name
    
  5. 找出价格在25到300之间的产品:

    store.inventory[?price >= `25` && price <= `300`]
    
  6. 按价格排序并获取最便宜的3个产品:

    sort_by(store.inventory, &price)[0:3].name
    

12.3 嵌套数据聚合

数据:

{
  "departments": [
    {
      "name": "Engineering",
      "teams": [
        {"name": "Backend", "members": ["Alice", "Bob"]},
        {"name": "Frontend", "members": ["Charlie", "David", "Eve"]}
      ]
    },
    {
      "name": "Sales",
      "teams": [
        {"name": "Direct", "members": ["Frank", "Grace"]},
        {"name": "Channel", "members": ["Henry"]}
      ]
    }
  ]
}

聚合查询:

  1. 获取所有团队名称:

    departments[*].teams[*].name
    
  2. 统计每个部门的团队数量:

    departments[*].{dept: name, team_count: length(teams)}
    
  3. 获取所有员工(扁平化):

    departments[*].teams[*].members[]
    
  4. 找出成员最多的团队:

    max_by(departments[*].teams[], &length(members))
    

13. 功能限制 ⚠️

13.1 标准JMESPath不支持的功能

  1. 算术运算: 不支持 +, -, *, /
  2. 条件表达式: 不支持 if-else 或三元运算符
  3. 正则表达式: 标准规范不包含正则支持
  4. 自定义函数: 需要通过编程语言扩展

13.2 扩展功能 🟡

以下功能在部分实现中可用:

  • split() - 字符串分割
  • group_by() - 分组
  • unique() - 去重
  • map() - 映射函数

使用前请确认你的JMESPath实现是否支持。


14. Python集成最佳实践

14.1 性能优化

import jmespath

# 好的做法:编译表达式
expression = jmespath.compile('users[?age > `30`].name')
result = expression.search(data)

# 不好的做法:每次都解析
result = jmespath.search('users[?age > `30`].name', data)

14.2 错误处理

import jmespath
from jmespath.exceptions import ParseError

def safe_search(expr, data, default=None):
    try:
        result = jmespath.search(expr, data)
        return result if result is not None else default
    except ParseError:
        return default

14.3 批量处理

def batch_extract(data, expressions):
    """批量提取多个值"""
    results = {}
    for key, expr in expressions.items():
        results[key] = jmespath.search(expr, data)
    return results

# 使用示例
queries = {
    'total_users': 'length(users)',
    'active_count': 'length(users[?active])',
    'user_names': 'users[*].name'
}
results = batch_extract(data, queries)

15. 快速参考

基础语法

标识符:        field, user.name
特殊字符键:    "field-name", "123abc"
索引:          array[0], array[-1]
切片:          array[0:5], array[::2], array[::-1]

投影

列表投影:      array[*].field
对象投影:      object.*.field
扁平化:        array[]
切片投影:      array[0:3].field

多选

多选列表:      [field1, field2]
多选哈希:      {key1: field1, key2: field2}

特殊符号

当前节点:      @
表达式引用:    &expression
字面量:        `value`
管道:          expression | expression

过滤

基本过滤:      array[?condition]
比较:          ==, !=, <, <=, >, >=
逻辑:          &&, ||, !

常用函数

类型:          type(), length()
数组:          sort(), reverse(), min(), max(), sum(), avg()
对象:          keys(), values(), merge()
字符串:        contains(), starts_with(), ends_with(), join()
高级:          sort_by(), min_by(), max_by()
转换:          to_string(), to_number(), to_array(), not_null()

学习建议

  1. 按照章节顺序学习,不要跳跃
  2. 每个概念都动手实践
  3. 使用 https://jmespath.org/ 在线测试
  4. 参考快速参考卡复习语法
posted @ 2025-09-09 14:00  271374667  阅读(118)  评论(1)    收藏  举报