JsonPath 完全指南
适用于 Vue 项目的新手入门与团队内部学习文档
目录
1. JsonPath 简介
1.1 什么是 JsonPath
JsonPath 是一种用于从 JSON 文档中提取数据的查询语言,类似于 XML 文档中的 XPath。它允许开发者使用简洁的表达式来定位和访问嵌套的 JSON 对象中的特定值。
1.2 JsonPath vs XPath
| 特性 | JsonPath | XPath |
|---|---|---|
| 作用对象 | JSON 数据 | XML 文档 |
| 根节点表示 | $ | / |
| 子节点分隔符 | . | / |
| 递归查找 | .. | // |
| 数组索引 | [0] | *[1] |
1.3 为什么需要 JsonPath
传统方式的问题:
// 冗长的链式调用
const title = data.store.book[0].title;
const prices = data.store.book.map(book => book.price);
const cheapBooks = data.store.book.filter(book => book.price < 10);
JsonPath 的优势:
- 表达式简洁,易于阅读
- 无需手动遍历多层嵌套结构
- 支持强大的过滤和查询功能
- 代码更少,可维护性更好
// JsonPath 简洁表达
const title = jsonpath.query(data, '$.store.book[0].title');
const prices = jsonpath.query(data, '$.store.book[*].price');
const cheapBooks = jsonpath.query(data, '$.store.book[?(@.price < 10)]');
2. 环境配置
2.1 安装 jsonpath-plus
在 Vue 项目中,推荐使用 jsonpath-plus 库,它是 JsonPath 的完整实现。
yarn add jsonpath-plus
2.2 在 Vue 项目中引入
方式一:直接引入
import { JSONPath } from 'jsonpath-plus';
const result = JSONPath({ path: '$.store.book[*].title', json: data });
方式二:作为全局工具方法
在 src/utils/index.js 中创建工具方法:
// src/utils/index.js
import { JSONPath } from 'jsonpath-plus';
/**
* JsonPath 查询工具
* @param {Object} data - JSON 数据
* @param {String} path - JsonPath 表达式
* @returns {Array} 查询结果
*/
export const jsonpath = (data, path) => {
try {
return JSONPath({ path, json: data });
} catch (error) {
console.error('JsonPath 查询失败:', error);
return [];
}
};
/**
* 获取单个值(非数组)
* @param {Object} data - JSON 数据
* @param {String} path - JsonPath 表达式
* @returns {*} 查询结果
*/
export const jsonpathValue = (data, path) => {
const result = jsonpath(data, path);
return result.length === 1 ? result[0] : result;
};
在组件中使用
import { jsonpath, jsonpathValue } from '@/utils';
import { defineComponent, ref } from '@vue/composition-api';
export default defineComponent({
setup() {
const data = ref({
users: [
{ id: 1, name: '张三', age: 25 },
{ id: 2, name: '李四', age: 30 }
]
});
// 查询所有用户名
const allNames = jsonpath(data.value, '$.users[*].name');
// ['张三', '李四']
// 查询第一个用户
const firstUser = jsonpathValue(data.value, '$.users[0]');
// { id: 1, name: '张三', age: 25 }
return {
allNames,
firstUser
};
}
});
3. 基础语法
3.1 根节点
$ 表示 JSON 数据的根节点。
// 基础数据
const data = {
users: [
{ id: 1, name: '张三' },
{ id: 2, name: '李四' }
]
};
// 获取整个根节点
JSONPath({ path: '$', json: data });
// [data]
// 从根节点开始查询
JSONPath({ path: '$.users', json: data });
// [users数组]
3.2 子节点访问
. 用于访问子节点。
const data = {
store: {
book: [
{ title: '书A', price: 10 },
{ title: '书B', price: 20 }
]
}
};
// 访问 store
JSONPath({ path: '$.store', json: data });
// 访问 book
JSONPath({ path: '$.store.book', json: data });
// 访问第一本书的 title
JSONPath({ path: '$.store.book[0].title', json: data });
// ['书A']
3.3 递归查找
.. 用于递归查找所有匹配的节点。
const data = {
store: {
book: [
{ price: 10 },
{ price: 20 }
],
electronics: [
{ price: 100 }
]
}
};
// 查找所有 price 节点
JSONPath({ path: '$..price', json: data });
// [10, 20, 100]
// 查找所有 book 节点
JSONPath({ path: '$..book', json: data });
3.4 通配符
* 表示匹配任意字段名。
const data = {
users: {
zhangsan: { age: 25 },
lisi: { age: 30 },
wangwu: { age: 28 }
}
};
// 获取 users 下所有子节点
JSONPath({ path: '$.users.*', json: data });
// [{ age: 25 }, { age: 30 }, { age: 28 }]
// 获取所有用户年龄
JSONPath({ path: '$.users.*.age', json: data });
// [25, 30, 28]
3.5 数组索引
[] 用于访问数组元素。
const data = {
items: ['a', 'b', 'c', 'd', 'e']
};
// 获取第一个元素
JSONPath({ path: '$.items[0]', json: data });
// ['a']
// 获取最后一个元素
JSONPath({ path: '$.items[-1]', json: data });
// ['e']
// 获取指定范围的元素(索引 1-3)
JSONPath({ path: '$.items[1:3]', json: data });
// ['b', 'c']
// 获取多个指定索引的元素
JSONPath({ path: '$.items[0,2,4]', json: data });
// ['a', 'c', 'e']
// 获取所有元素
JSONPath({ path: '$.items[*]', json: data });
// ['a', 'b', 'c', 'd', 'e']
4. 详细操作符说明
4.1 过滤表达式 ?()
使用 ?() 进行条件过滤,@ 代表当前节点。
4.1.1 比较运算符
const data = {
products: [
{ id: 1, name: '商品A', price: 100, stock: 50 },
{ id: 2, name: '商品B', price: 200, stock: 30 },
{ id: 3, name: '商品C', price: 50, stock: 0 }
]
};
// 价格大于 100 的商品
JSONPath({
path: '$.products[?(@.price > 100)]',
json: data
});
// [{ id: 2, name: '商品B', price: 200, stock: 30 }]
// 价格小于等于 100 的商品
JSONPath({
path: '$.products[?(@.price <= 100)]',
json: data
});
// [{ id: 1, ... }, { id: 3, ... }]
// 库存为 0 的商品
JSONPath({
path: '$.products[?(@.stock == 0)]',
json: data
});
// [{ id: 3, name: '商品C', price: 50, stock: 0 }]
4.1.2 逻辑运算符
const data = {
products: [
{ id: 1, name: '商品A', price: 100, stock: 50, category: 'electronic' },
{ id: 2, name: '商品B', price: 200, stock: 30, category: 'book' },
{ id: 3, name: '商品C', price: 150, stock: 0, category: 'electronic' }
]
};
// 价格 >= 100 且库存 > 0
JSONPath({
path: '$.products[?(@.price >= 100 && @.stock > 0)]',
json: data
});
// 价格 < 200 或 库存 < 10
JSONPath({
path: '$.products[?(@.price < 200 || @.stock < 10)]',
json: data
});
// 电子类且价格大于 100
JSONPath({
path: '$.products[?(@.category == "electronic" && @.price > 100)]',
json: data
});
4.1.3 包含运算符
const data = {
users: [
{ id: 1, name: '张三', tags: ['admin', 'vip'] },
{ id: 2, name: '李四', tags: ['user'] },
{ id: 3, name: '王五', tags: ['admin', 'moderator'] }
]
};
// 包含 admin 标签的用户
JSONPath({
path: '$.users[?(@.tags && @.tags.indexOf("admin") != -1)]',
json: data
});
// 名字包含"张"的用户
JSONPath({
path: '$.users[?(@.name.indexOf("张") != -1)]',
json: data
});
4.2 范围切片 [start:end:step]
const data = {
numbers: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
items: ['a', 'b', 'c', 'd', 'e']
};
// 获取前 3 个元素
JSONPath({ path: '$.numbers[0:3]', json: data });
// [1, 2, 3]
// 获取索引 2 到 4 的元素
JSONPath({ path: '$.numbers[2:5]', json: data });
// [3, 4, 5]
// 从索引 1 到结束
JSONPath({ path: '$.numbers[1:]', json: data });
// [2, 3, 4, 5, 6, 7, 8, 9, 10]
// 每 2 个取一个
JSONPath({ path: '$.numbers[::2]', json: data });
// [1, 3, 5, 7, 9]
// 倒序获取
JSONPath({ path: '$.numbers[::-1]', json: data });
// [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
4.3 多选索引
const data = {
products: [
{ id: 1, name: '商品A' },
{ id: 2, name: '商品B' },
{ id: 3, name: '商品C' },
{ id: 4, name: '商品D' },
{ id: 5, name: '商品E' }
]
};
// 获取索引 0, 2, 4 的商品
JSONPath({ path: '$.products[0,2,4]', json: data });
// [{ id: 1, ... }, { id: 3, ... }, { id: 5, ... }]
// 嵌套数组多选
const nestedData = {
matrices: [
[[1, 2, 3], [4, 5, 6]],
[[7, 8, 9], [10, 11, 12]]
]
};
// 获取特定元素
JSONPath({ path: '$.matrices[0][0][1]', json: nestedData });
// [2]
4.4 脚本表达式
const data = {
products: [
{ id: 1, name: '商品A', price: 100 },
{ id: 2, name: '商品B', price: 200 },
{ id: 3, name: '商品C', price: 150 }
]
};
// 计算价格总和
JSONPath({
path: '$.products[*].price',
json: data
}).reduce((sum, price) => sum + price, 0);
// 转换数据格式
JSONPath({
path: '$.products[*]',
json: data
}).map(item => ({
label: item.name,
value: item.id
}));
4.5 其他操作符
4.5.1 长度运算符
const data = {
items: [1, 2, 3, 4, 5],
users: [{ name: '张三' }, { name: '李四' }]
};
// 获取数组长度
JSONPath({ path: '$.items.length()', json: data });
// 脚本表达式方式,jsonpath-plus 不直接支持 length(),需使用其他方式
// 推荐方式:获取后使用 JavaScript 处理
const items = jsonpath(data, '$.items[*]');
const length = items.length;
4.5.2 存在性检查
const data = {
users: [
{ id: 1, name: '张三', email: 'zhangsan@example.com' },
{ id: 2, name: '李四' }
]
};
// 有邮箱的用户
JSONPath({
path: '$.users[?(@.email)]',
json: data
});
// [{ id: 1, name: '张三', email: 'zhangsan@example.com' }]
4.6 操作符速查表
| 操作符 | 说明 | 示例 |
|---|---|---|
$ | 根节点 | $.users |
@ | 当前节点(过滤中使用) | ?(@.price > 10) |
. | 子节点访问 | $.users.name |
.. | 递归查找 | $..price |
* | 通配符 | $.users.* |
[] | 数组索引/过滤 | .users[0] / [?(@.x > 10)] |
?() | 过滤表达式 | [?(@.price > 10)] |
[start:end:step] | 范围切片 | [0:5:2] |
4.7 Functions(函数列表)
JsonPath 及 jsonpath-plus 库支持一系列内置函数,这些函数可以在表达式中使用来处理数据。
4.7.1 数学函数
| 函数 | 说明 | 示例 | 返回值 |
|---|---|---|---|
min() | 获取数组中的最小值 | min($.prices) | 最小值 |
max() | 获取数组中的最大值 | max($.prices) | 最大值 |
avg() | 计算数组的平均值 | avg($.prices) | 平均值 |
sum() | 计算数组元素的总和 | sum($.prices) | 总和 |
length() | 获取数组或字符串长度 | length($.items) | 长度值 |
const data = {
products: [
{ price: 10, quantity: 2 },
{ price: 20, quantity: 3 },
{ price: 30, quantity: 1 }
]
};
// 提取所有价格
const prices = jsonpath(data, '$.products[*].price');
// [10, 20, 30]
// 使用 JavaScript 计算统计值(推荐)
const minPrice = Math.min(...prices);
const maxPrice = Math.max(...prices);
const avgPrice = prices.reduce((sum, p) => sum + p, 0) / prices.length;
const sumPrice = prices.reduce((sum, p) => sum + p, 0);
4.7.2 字符串函数
| 函数 | 说明 | 示例 | 返回值 |
|---|---|---|---|
length() | 获取字符串长度 | length($.name) | 字符串长度 |
concat() | 连接字符串 | concat($.firstName, $.lastName) | 拼接后的字符串 |
const data = {
users: [
{ firstName: '张', lastName: '三' },
{ firstName: '李', lastName: '四' }
]
};
// 获取用户全名(使用 JavaScript 处理)
const fullNames = jsonpath(data, '$.users[*]').map(user =>
`${user.firstName}${user.lastName}`
);
// ['张三', '李四']
4.7.3 数组函数
| 函数 | 说明 | 示例 | 返回值 |
|---|---|---|---|
length() / size() | 获取数组长度 | length($.items) | 数组长度 |
count() | 统计数组元素数量 | count($.items) | 元素数量 |
const data = {
items: [1, 2, 3, 4, 5],
products: [
{ id: 1, name: '商品A' },
{ id: 2, name: '商品B' },
{ id: 3, name: '商品C' }
]
};
// 获取数组长度
const items = jsonpath(data, '$.items[*]');
const itemsCount = items.length;
const products = jsonpath(data, '$.products[*]');
const productsCount = products.length;
4.7.4 类型检查函数
| 函数 | 说明 | 示例 | 返回值 |
|---|---|---|---|
type() | 获取值的类型 | type($.value) | 类型字符串 |
const data = {
stringValue: 'hello',
numberValue: 123,
booleanValue: true,
arrayValue: [1, 2, 3],
objectValue: { key: 'value' },
nullValue: null
};
// 使用 JavaScript 获取类型
const stringValue = data.stringValue;
console.log(typeof stringValue); // 'string'
console.log(Array.isArray(data.arrayValue)); // true
4.7.5 实用函数
| 函数 | 说明 | 示例 | 返回值 |
|---|---|---|---|
keys() | 获取对象的所有键 | keys($.user) | 键数组 |
values() | 获取对象的所有值 | values($.user) | 值数组 |
const data = {
user: {
name: '张三',
age: 25,
city: '北京'
}
};
// 获取对象键
const user = jsonpathValue(data, '$.user');
const keys = Object.keys(user);
// ['name', 'age', 'city']
// 获取对象值
const values = Object.values(user);
// ['张三', 25, '北京']
4.7.6 函数使用注意事项
⚠️ 重要提示:
- jsonpath-plus 限制:jsonpath-plus 库对函数的支持有限,许多函数需要通过 JavaScript 处理
- 推荐做法:使用 JsonPath 提取数据后,用 JavaScript 进行计算和处理
- 性能考虑:对于复杂计算,使用 JavaScript 原生方法通常更快
// 推荐的函数使用模式
const products = jsonpath(data, '$.products[*]');
// 使用 JavaScript 进行各种计算
const total = products.reduce((sum, p) => sum + p.price, 0);
const average = total / products.length;
const maxPrice = Math.max(...products.map(p => p.price));
const minPrice = Math.min(...products.map(p => p.price));
4.8 Filter Operators(过滤运算符)
过滤运算符用于在 ?() 过滤表达式中进行条件判断。以下是完整的过滤运算符列表及使用示例。
4.8.1 比较运算符
| 运算符 | 说明 | 示例 |
|---|---|---|
== | 等于 | [?(@.age == 25)] |
!= | 不等于 | [?(@.status != 'active')] |
=== | 严格等于(值和类型) | [?(@.value === 10)] |
!== | 严格不等于 | [?(@.value !== null)] |
< | 小于 | [?(@.price < 100)] |
> | 大于 | [?(@.count > 5)] |
<= | 小于等于 | [?(@.score <= 90)] |
>= | 大于等于 | [?(@.age >= 18)] |
const data = {
users: [
{ id: 1, name: '张三', age: 25, score: 85, active: true },
{ id: 2, name: '李四', age: 30, score: 92, active: false },
{ id: 3, name: '王五', age: 20, score: 78, active: true },
{ id: 4, name: '赵六', age: 28, score: 88, active: true }
]
};
// 年龄等于 25 的用户
jsonpath(data, '$.users[?(@.age == 25)]');
// [{ id: 1, name: '张三', age: 25, ... }]
// 激活状态不等于 false 的用户
jsonpath(data, '$.users[?(@.active != false)]');
// [{ id: 1, ... }, { id: 3, ... }, { id: 4, ... }]
// 年龄小于 25 的用户
jsonpath(data, '$.users[?(@.age < 25)]');
// [{ id: 3, name: '王五', age: 20, ... }]
// 分数大于等于 90 的用户
jsonpath(data, '$.users[?(@.score >= 90)]');
// [{ id: 2, name: '李四', score: 92, ... }]
4.8.2 逻辑运算符
| 运算符 | 说明 | 示例 |
|---|---|---|
&& | 逻辑与(并且) | [?(@.age > 18 && @.active)] |
|| | 逻辑或(或者) | [?(@.role == "admin" || @.role == "moderator")] |
! | 逻辑非(取反) | [?(!@.deleted)] |
const data = {
users: [
{ name: '张三', age: 25, role: 'admin', active: true, deleted: false },
{ name: '李四', age: 30, role: 'user', active: false, deleted: false },
{ name: '王五', age: 20, role: 'admin', active: true, deleted: true },
{ name: '赵六', age: 28, role: 'moderator', active: true, deleted: false }
]
};
// 年龄大于 18 且是激活状态的用户
jsonpath(data, '$.users[?(@.age > 18 && @.active)]');
// [张三, 赵六]
// 角色是 admin 或 moderator 的用户
jsonpath(data, '$.users[?(@.role == "admin" || @.role == "moderator")]');
// [张三, 王五, 赵六]
// 未被删除的用户
jsonpath(data, '$.users[?(!@.deleted)]');
// [张三, 李四, 赵六]
4.8.3 算术运算符
| 运算符 | 说明 | 示例 |
|---|---|---|
+ | 加法 | [?(@.price + @.tax > 100)] |
- | 减法 | [?(@.age - @.startAge > 5)] |
* | 乘法 | [?(@.price * @.quantity > 1000)] |
/ | 除法 | [?(@.total / @.items > 100)] |
% | 取模(求余) | [?(@.count % 2 == 0)] |
const data = {
products: [
{ name: '商品A', price: 80, tax: 25 },
{ name: '商品B', price: 50, tax: 30 },
{ name: '商品C', price: 60, tax: 20 },
{ name: '商品D', price: 40, tax: 10 }
]
};
// 价格加税后超过 100 的商品
jsonpath(data, '$.products[?(@.price + @.tax > 100)]');
// [{ name: '商品A', price: 80, tax: 25 }]
// 价格减去折扣后大于 50 的商品(假设折扣为 20)
jsonpath(data, '$.products[?(@.price - 20 > 50)]');
// [{ name: '商品A', price: 80, tax: 25 }, { name: '商品C', price: 60, tax: 20 }]
// 数量大于 0 且是偶数的记录
const items = [
{ id: 1, count: 5 },
{ id: 2, count: 6 },
{ id: 3, count: 8 }
];
jsonpath(items, '$[?(@.count % 2 == 0)]');
// [{ id: 2, count: 6 }, { id: 3, count: 8 }]
4.8.4 字符串运算符
| 运算符 | 说明 | 示例 |
|---|---|---|
== | 字符串相等(区分大小写) | [?(@.name == "张三")] |
!= | 字符串不等 | [?(@.status != "deleted")] |
+ | 字符串拼接 | [?(@.firstName + @.lastName == "张三")] |
const data = {
users: [
{ name: '张三', email: 'zhangsan@example.com' },
{ name: '李四', email: 'lisi@example.com' },
{ name: 'zhangsan', email: 'test@example.com' }
]
};
// 名字等于"张三"的用户
jsonpath(data, '$.users[?(@.name == "张三")]');
// [{ name: '张三', email: 'zhangsan@example.com' }]
// 名字不等于"张三"的用户
jsonpath(data, '$.users[?(@.name != "张三")]');
// [{ name: '李四', ... }, { name: 'zhangsan', ... }]
4.8.5 数组运算符
| 运算符 | 说明 | 示例 |
|---|---|---|
[] | 数组索引 | $.users[?] |
[start:end] | 数组切片 | $.items[0:5] |
[condition] | 数组过滤 | $.items[?(@.price > 100)] |
const data = {
items: [
{ id: 1, name: '项目A', priority: 1 },
{ id: 2, name: '项目B', priority: 2 },
{ id: 3, name: '项目C', priority: 1 },
{ id: 4, name: '项目D', priority: 3 },
{ id: 5, name: '项目E', priority: 2 }
]
};
// 获取前 3 个项目
jsonpath(data, '$.items[0:3]');
// [项目A, 项目B, 项目C]
// 获取优先级为 1 的项目
jsonpath(data, '$.items[?(@.priority == 1)]');
// [项目A, 项目C]
4.8.6 存在性运算符
| 运算符 | 说明 | 示例 |
|---|---|---|
@.field | 字段存在检查(truthy 检查) | [?(@.email)] |
@.field !== undefined | 明确检查字段不为 undefined | [?(@.email !== undefined)] |
@.field !== null | 明确检查字段不为 null | [?(@.email !== null)] |
const data = {
users: [
{ id: 1, name: '张三', email: 'zhangsan@example.com' },
{ id: 2, name: '李四', email: '' },
{ id: 3, name: '王五' },
{ id: 4, name: '赵六', email: null }
]
};
// 有邮箱字段(truthy 值)的用户
jsonpath(data, '$.users[?(@.email)]');
// [{ id: 1, name: '张三', email: 'zhangsan@example.com' }]
// 有邮箱字段(不为 undefined 或 null)的用户
jsonpath(data, '$.users[?(@.email !== null && @.email !== undefined)]');
// [{ id: 1, email: 'zhangsan@example.com' },
// { id: 2, email: '' },
// { id: 4, email: null }]
4.8.7 模式匹配运算符
| 运算符 | 说明 | 示例 | 注意 |
|---|---|---|---|
indexOf() | 字符串包含检查 | [?(@.name.indexOf("张") != -1)] | JavaScript 方法 |
match() | 正则匹配 | [?(@.email.match(/@/))] | JavaScript 方法 |
startsWith() | 以...开头 | [?(@.name.startsWith("张"))] | JavaScript 方法 |
endsWith() | 以...结尾 | [?(@.url.endsWith(".com"))] | JavaScript 方法 |
const data = {
users: [
{ name: '张三', email: 'zhangsan@example.com' },
{ name: '李四', email: 'lisi@test.org' },
{ name: '张伟', email: 'zhangwei@example.com' },
{ name: '王芳', email: 'wangfang@test.com' }
]
};
// 名字包含"张"的用户
jsonpath(data, '$.users[?(@.name.indexOf("张") != -1)]');
// [{ name: '张三', ... }, { name: '张伟', ... }]
// 邮箱匹配 example.com 域名的用户
jsonpath(data, '$.users[?(@.email.match(/example\.com$/))]');
// [{ name: '张三', ... }, { name: '张伟', ... }]
// 名字以"张"开头的用户
jsonpath(data, '$.users[?(@.name.startsWith("张"))]');
// [{ name: '张三', ... }, { name: '张伟', ... }]
// 邮箱以".com"结尾的用户
jsonpath(data, '$.users[?(@.email.endsWith(".com"))]');
// [{ name: '张三', ... }, { name: '张伟', ... }, { name: '王芳', ... }]
4.8.8 过滤运算符使用技巧
技巧 1:嵌套过滤条件
// 复杂的嵌套条件
jsonpath(data, '$.products[?(@.category == "electronic" && (@.price > 100 || @.stock > 50))]');
技巧 2:组合多个过滤表达式
// 先按类别过滤,再按价格过滤
const electronics = jsonpath(data, '$.products[?(@.category == "electronic")]');
const result = jsonpath(electronics, '$[?(@.price > 100)]');
技巧 3:使用 JavaScript 方法增强过滤能力
// 结合 JavaScript 方法进行更复杂的过滤
const users = jsonpath(data, '$.users[*]');
const filtered = users.filter(user =>
user.age >= 18 &&
user.active &&
user.email &&
user.email.includes('@')
);
4.8.9 运算符优先级
从高到低的运算符优先级:
()- 括号(最高优先级)-(负号)!(非)*/%+-<<=>>===!====!==&&||(最低优先级)
// 使用括号明确优先级
jsonpath(data, '$.items[?((@.price * @.tax) + @.shipping > 100)]');
// 不使用括号时
jsonpath(data, '$.items[?(@.price * @.tax + @.shipping > 100)]');
// 等同于:先乘法后加法
5. 实战代码示例
5.1 Vue 组件中的基础使用
<template>
<div class="user-list">
<h2>用户列表</h2>
<ul>
<li v-for="user in users" :key="user.id">
{{ user.name }} - {{ user.role }}
</li>
</ul>
<h3>管理员列表</h3>
<ul>
<li v-for="admin in admins" :key="admin.id">
{{ admin.name }}
</li>
</ul>
</div>
</template>
<script>
import { defineComponent, ref, onMounted } from '@vue/composition-api';
import { jsonpath } from '@/utils';
export default defineComponent({
name: 'UserList',
setup() {
const users = ref([]);
const admins = ref([]);
const mockData = {
departments: [
{
name: '技术部',
employees: [
{ id: 1, name: '张三', role: 'admin' },
{ id: 2, name: '李四', role: 'user' }
]
},
{
name: '产品部',
employees: [
{ id: 3, name: '王五', role: 'user' },
{ id: 4, name: '赵六', role: 'admin' }
]
}
]
};
onMounted(() => {
// 提取所有用户
users.value = jsonpath(
mockData,
'$.departments[*].employees[*]'
);
// 提取所有管理员
admins.value = jsonpath(
mockData,
'$.departments[*].employees[?(@.role == "admin")]'
);
});
return {
users,
admins
};
}
});
</script>
5.2 动态查询组件
<template>
<div class="dynamic-query">
<div class="query-controls">
<q-select
v-model="selectedPath"
:options="pathOptions"
label="选择查询路径"
style="width: 300px"
/>
<q-button type="primary" @click="handleQuery">查询</q-button>
</div>
<div class="query-result">
<h3>查询结果</h3>
<pre>{{ JSON.stringify(queryResult, null, 2) }}</pre>
</div>
</div>
</template>
<script>
import { defineComponent, ref } from '@vue/composition-api';
import { jsonpath } from '@/utils';
export default defineComponent({
name: 'DynamicQuery',
setup() {
const selectedPath = ref('$.users[*].name');
const queryResult = ref([]);
const mockData = {
users: [
{ id: 1, name: '张三', age: 25, role: 'admin' },
{ id: 2, name: '李四', age: 30, role: 'user' },
{ id: 3, name: '王五', age: 28, role: 'user' }
]
};
const pathOptions = ref([
{ label: '所有用户名', value: '$.users[*].name' },
{ label: '所有用户年龄', value: '$.users[*].age' },
{ label: '所有管理员', value: '$.users[?(@.role == "admin")]' },
{ label: '年龄大于25的用户', value: '$.users[?(@.age > 25)]' }
]);
const handleQuery = () => {
queryResult.value = jsonpath(mockData, selectedPath.value);
};
return {
selectedPath,
queryResult,
pathOptions,
handleQuery
};
}
});
</script>
5.3 数据表格筛选
<template>
<div class="data-table-filter">
<div class="filter-bar">
<q-input
v-model="filters.name"
placeholder="按用户名筛选"
@input="applyFilters"
/>
<q-select
v-model="filters.role"
:options="roleOptions"
placeholder="按角色筛选"
clearable
@change="applyFilters"
/>
</div>
<q-table :columns="columns" :data="filteredData" />
</div>
</template>
<script>
import { defineComponent, ref, computed } from '@vue/composition-api';
import { jsonpath } from '@/utils';
import _ from 'lodash';
export default defineComponent({
name: 'DataTableFilter',
setup() {
const originalData = ref([
{ id: 1, name: '张三', age: 25, role: 'admin', department: '技术部' },
{ id: 2, name: '李四', age: 30, role: 'user', department: '产品部' },
{ id: 3, name: '王五', age: 28, role: 'user', department: '技术部' },
{ id: 4, name: '赵六', age: 32, role: 'admin', department: '运营部' }
]);
const filteredData = ref([]);
const filters = ref({
name: '',
role: null
});
const columns = ref([
{ field: 'name', label: '姓名' },
{ field: 'age', label: '年龄' },
{ field: 'role', label: '角色' },
{ field: 'department', label: '部门' }
]);
const roleOptions = ref([
{ label: '管理员', value: 'admin' },
{ label: '普通用户', value: 'user' }
]);
const applyFilters = () => {
let result = [...originalData.value];
// 使用 JsonPath 进行筛选
if (filters.value.name) {
const namePath = `$[?(@.name.indexOf("${filters.value.name}") != -1)]`;
result = jsonpath(result, namePath);
}
if (filters.value.role) {
const rolePath = `$[?(@.role == "${filters.value.role}")]`;
result = jsonpath(result, rolePath);
}
filteredData.value = result;
};
// 初始化显示所有数据
filteredData.value = originalData.value;
return {
filteredData,
filters,
columns,
roleOptions,
applyFilters
};
}
});
</script>
5.4 图表数据准备
<template>
<div class="chart-data-preparation">
<h2>销售数据统计</h2>
<div class="charts-container">
<div class="chart-box">
<h3>按产品分类统计</h3>
<pie-chart :data="categoryChartData" />
</div>
<div class="chart-box">
<h3>月度销售趋势</h3>
<line-chart :data="trendChartData" />
</div>
</div>
</div>
</template>
<script>
import { defineComponent, ref, onMounted } from '@vue/composition-api';
import { jsonpath } from '@/utils';
import _ from 'lodash';
export default defineComponent({
name: 'ChartDataPreparation',
setup() {
const categoryChartData = ref([]);
const trendChartData = ref([]);
const mockSalesData = {
year: 2024,
quarters: [
{
name: 'Q1',
months: [
{ name: '1月', sales: [
{ product: '电子产品', amount: 10000 },
{ product: '服装', amount: 5000 },
{ product: '食品', amount: 3000 }
]},
{ name: '2月', sales: [
{ product: '电子产品', amount: 12000 },
{ product: '服装', amount: 6000 },
{ product: '食品', amount: 3500 }
]},
{ name: '3月', sales: [
{ product: '电子产品', amount: 11000 },
{ product: '服装', amount: 5500 },
{ product: '食品', amount: 3200 }
]}
]
}
]
};
const prepareCategoryData = () => {
// 使用 JsonPath 提取所有销售记录
const allSales = jsonpath(
mockSalesData,
'$.quarters[*].months[*].sales[*]'
);
// 按产品分类汇总
const grouped = _.groupBy(allSales, 'product');
const result = Object.keys(grouped).map(key => ({
name: key,
value: _.sumBy(grouped[key], 'amount')
}));
categoryChartData.value = result;
};
const prepareTrendData = () => {
// 使用 JsonPath 提取所有月份数据
const months = jsonpath(
mockSalesData,
'$.quarters[*].months[*]'
);
// 计算每月总销售额
const result = months.map(month => ({
name: month.name,
value: _.sumBy(month.sales, 'amount')
}));
trendChartData.value = result;
};
onMounted(() => {
prepareCategoryData();
prepareTrendData();
});
return {
categoryChartData,
trendChartData
};
}
});
</script>
6. 常用场景案例
6.1 API 响应数据处理
import { jsonpath, jsonpathValue } from '@/utils';
// 场景:从复杂的 API 响应中提取所需数据
const apiResponse = {
code: 200,
message: 'success',
data: {
pageInfo: {
total: 100,
page: 1,
pageSize: 10
},
items: [
{ id: 1, name: '资产A', type: 'server', tags: ['production'] },
{ id: 2, name: '资产B', type: 'database', tags: ['production', 'critical'] },
{ id: 3, name: '资产C', type: 'server', tags: ['testing'] }
],
meta: {
version: '1.0',
timestamp: '2024-01-15T10:30:00Z'
}
}
};
// 提取列表数据
const assets = jsonpath(apiResponse, '$.data.items[*]');
// 提取分页信息
const totalCount = jsonpathValue(apiResponse, '$.data.pageInfo.total');
// 提取生产环境资产
const productionAssets = jsonpath(
apiResponse,
'$.data.items[?(@.tags.indexOf("production") != -1)]'
);
// 提取所有资产名称
const assetNames = jsonpath(apiResponse, '$.data.items[*].name');
6.2 配置文件解析
// 场景:从配置文件中提取特定配置项
const config = {
app: {
name: '资产管理',
version: '1.0.0',
settings: {
database: {
host: 'localhost',
port: 5432,
name: 'assets'
},
cache: {
enabled: true,
ttl: 3600
},
features: {
upload: true,
export: true,
notification: false
}
}
},
thresholds: {
cpu: 80,
memory: 90,
disk: 85
}
};
// 提取数据库配置
const dbConfig = jsonpath(config, '$.app.settings.database');
// 提取所有启用的功能
const enabledFeatures = jsonpath(
config,
'$.app.settings.features[?(@ == true)]'
);
// 提取所有阈值
const allThresholds = jsonpath(config, '$.thresholds.*');
6.3 表格数据筛选
// 场景:前端表格的多条件筛选
const tableData = [
{ id: 1, name: '张三', age: 25, department: '研发', status: 'active' },
{ id: 2, name: '李四', age: 30, department: '产品', status: 'inactive' },
{ id: 3, name: '王五', age: 28, department: '研发', status: 'active' },
{ id: 4, name: '赵六', age: 32, department: '设计', status: 'active' }
];
// 筛选研发部门的激活用户
const activeDevUsers = jsonpath(
tableData,
'$[?(@.department == "研发" && @.status == "active")]'
);
// 筛选年龄大于 28 的用户
const seniorUsers = jsonpath(
tableData,
'$[?(@.age > 28)]'
);
// 组合筛选(链式查询)
const result = jsonpath(
jsonpath(tableData, '$[?(@.status == "active")]'),
'$[?(@.department == "研发")]'
);
6.4 图表数据准备
// 场景:准备 ECharts 图表数据
const rawData = {
statistics: {
byType: [
{ type: 'server', count: 45 },
{ type: 'database', count: 23 },
{ type: 'storage', count: 12 }
],
byRisk: [
{ level: 'critical', count: 5 },
{ level: 'high', count: 15 },
{ level: 'medium', count: 30 },
{ level: 'low', count: 25 }
],
byTime: [
{ date: '2024-01', count: 10 },
{ date: '2024-02', count: 15 },
{ date: '2024-03', count: 20 },
{ date: '2024-04', count: 18 },
{ date: '2024-05', count: 22 }
]
}
};
// 饼图数据
const pieData = jsonpath(rawData, '$.statistics.byType[*]').map(item => ({
name: item.type,
value: item.count
}));
// 柱状图数据
const barData = {
categories: jsonpath(rawData, '$.statistics.byRisk[*].level'),
values: jsonpath(rawData, '$.statistics.byRisk[*].count')
};
// 折线图数据
const lineData = {
xAxis: jsonpath(rawData, '$.statistics.byTime[*].date'),
series: jsonpath(rawData, '$.statistics.byTime[*].count')
};
6.5 数据验证
// 场景:验证数据完整性
const formData = {
user: {
name: '张三',
email: 'zhangsan@example.com',
phone: '13800138000'
},
addresses: [
{ type: 'home', city: '北京', street: 'xxx路' },
{ type: 'work', city: '北京', street: 'yyy路' }
],
preferences: {
notifications: true,
language: 'zh-CN'
}
};
// 验证必填字段
const requiredPaths = [
'$.user.name',
'$.user.email',
'$.user.phone'
];
const requiredFields = {};
requiredPaths.forEach(path => {
const result = jsonpath(formData, path);
const fieldName = path.split('.').pop();
requiredFields[fieldName] = result.length > 0 && result[0] !== '';
});
// 检查地址是否存在
const hasAddresses = jsonpath(formData, '$.addresses[0]').length > 0;
// 验证邮箱格式
const emails = jsonpath(formData, '$.user.email');
if (emails.length > 0) {
const isValidEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(emails[0]);
}
7. 性能优化建议
7.1 避免递归查询
递归查询 .. 会遍历整个 JSON 结构,性能开销较大。
// ❌ 不推荐:使用递归查询
const allPrices = jsonpath(data, '$..price');
// ✅ 推荐:明确指定路径
const allPrices = jsonpath(data, '$.store.book[*].price');
7.2 缓存查询结果
对于频繁使用的查询结果,应该进行缓存。
import { ref } from '@vue/composition-api';
// 创建缓存工具
const createJsonPathCache = () => {
const cache = new Map();
return {
query(data, path) {
const cacheKey = `${JSON.stringify(data)}-${path}`;
if (cache.has(cacheKey)) {
return cache.get(cacheKey);
}
const result = jsonpath(data, path);
cache.set(cacheKey, result);
return result;
},
clear() {
cache.clear();
}
};
};
export const jsonPathCache = createJsonPathCache();
7.3 简化表达式
复杂的嵌套表达式会影响解析性能,应尽量简化。
// ❌ 不推荐:复杂的嵌套过滤
const result = jsonpath(
data,
'$.items[?(@.price > 100 && @.stock > 0 && @.category == "electronic")]'
);
// ✅ 推荐:分步查询
const electronicItems = jsonpath(data, '$.items[?(@.category == "electronic")]');
const result = jsonpath(
electronicItems,
'$[?(@.price > 100 && @.stock > 0)]'
);
7.4 选择合适的库
根据实际需求选择合适的 JsonPath 实现。
| 库名 | 大小 | 特点 | 适用场景 |
|---|---|---|---|
| jsonpath-plus | ~50KB | 功能完整,兼容性好 | 需要完整功能的项目 |
| jsonpath | ~10KB | 轻量级,基础功能 | 简单查询场景 |
| @tsced/parser | ~8KB | TypeScript 支持 | TypeScript 项目 |
7.5 批量查询优化
对于多个相关查询,可以考虑一次性获取后使用 JavaScript 处理。
// ❌ 不推荐:多次查询
const names = jsonpath(data, '$.users[*].name');
const ages = jsonpath(data, '$.users[*].age');
const emails = jsonpath(data, '$.users[*].email');
// ✅ 推荐:一次查询,JavaScript 处理
const users = jsonpath(data, '$.users[*]');
const names = users.map(u => u.name);
const ages = users.map(u => u.age);
const emails = users.map(u => u.email);
7.6 使用计算属性缓存
在 Vue 组件中使用计算属性自动缓存结果。
import { defineComponent, ref, computed } from '@vue/composition-api';
import { jsonpath } from '@/utils';
export default defineComponent({
setup() {
const data = ref({ /* ... */ });
// 使用计算属性自动缓存
const admins = computed(() => {
return jsonpath(data.value, '$.users[?(@.role == "admin")]');
});
const activeUsers = computed(() => {
return jsonpath(data.value, '$.users[?(@.status == "active")]');
});
return {
admins,
activeUsers
};
}
});
8. 常见问题
8.1 错误处理
import { jsonpath } from '@/utils';
// 添加错误边界
const safeJsonPath = (data, path, defaultValue = []) => {
try {
const result = jsonpath(data, path);
return result.length > 0 ? result : defaultValue;
} catch (error) {
console.error(`JsonPath 查询失败: ${path}`, error);
return defaultValue;
}
};
// 使用示例
const users = safeJsonPath(data, '$.users[*]', []);
const firstUser = safeJsonPath(data, '$.users[0]', null);
8.2 调试技巧
// 创建调试工具
const debugJsonPath = (data, path) => {
console.log('=== JsonPath 调试 ===');
console.log('路径:', path);
console.log('数据类型:', typeof data);
console.log('数据:', JSON.stringify(data, null, 2));
try {
const result = jsonpath(data, path);
console.log('查询结果:', result);
console.log('结果数量:', result.length);
return result;
} catch (error) {
console.error('查询失败:', error.message);
return [];
}
};
// 使用
debugJsonPath(data, '$.users[?(@.age > 25)]');
8.3 常见错误
错误 1:路径语法错误
// ❌ 错误:路径中包含不正确的引号
jsonpath(data, '$.users[?(@.name == "张三")]');
// ✅ 正确:确保引号转义
jsonpath(data, '$.users[?(@.name == "张三")]');
错误 2:忽略查询结果是数组
// ❌ 错误:直接使用结果作为对象
const user = jsonpath(data, '$.users[0]');
console.log(user.name); // 错误:user 是数组
// ✅ 正确:处理数组结果
const users = jsonpath(data, '$.users[0]');
const user = users.length > 0 ? users[0] : null;
if (user) {
console.log(user.name);
}
错误 3:过度使用递归查询
// ❌ 错误:使用递归查询导致性能问题
const result = jsonpath(largeData, '$..price');
// ✅ 正确:明确指定路径
const result = jsonpath(largeData, '$.data.items[*].price');
8.4 最佳实践
- 明确的路径:尽量使用明确的路径而非通配符和递归
- 错误处理:始终添加 try-catch 或使用包装函数
- 性能考虑:对大对象使用分步查询
- 类型检查:处理查询结果时进行类型检查
- 代码可读性:将复杂的 JsonPath 表达式提取为常量
// 定义常用的 JsonPath 表达式
const JSONPATH_CONSTANTS = {
ALL_USERS: '$.users[*]',
ADMIN_USERS: '$.users[?(@.role == "admin")]',
ACTIVE_USERS: '$.users[?(@.status == "active")]',
USER_NAMES: '$.users[*].name'
};
// 使用
const admins = jsonpath(data, JSONPATH_CONSTANTS.ADMIN_USERS);
8.5 Vue Composition API 集成
// 封装 JsonPath Hook
import { ref, computed } from '@vue/composition-api';
import { jsonpath } from '@/utils';
export function useJsonPath(dataRef) {
const query = (path) => {
return computed(() => jsonpath(dataRef.value, path));
};
const getValue = (path, defaultValue = null) => {
return computed(() => {
const result = jsonpath(dataRef.value, path);
return result.length === 1 ? result[0] : defaultValue;
});
};
return {
query,
getValue
};
}
// 在组件中使用
import { ref } from '@vue/composition-api';
import { useJsonPath } from '@/hooks/use-jsonpath';
export default defineComponent({
setup() {
const data = ref({ /* ... */ });
const { query, getValue } = useJsonPath(data);
const admins = query('$.users[?(@.role == "admin")]');
const totalCount = getValue('$.total', 0);
return {
admins,
totalCount
};
}
});
附录
A. 参考资料
B. 在线工具
C. 更新日志
- v1.0.0 (2024-01-15): 初始版本,包含基础语法和常用场景
总结
JsonPath 是一个强大的 JSON 数据查询工具,在 Vue 项目中可以大大简化数据处理逻辑。通过本文档的学习,你应该能够:
- 理解 JsonPath 的基本概念和语法
- 在 Vue 项目中正确使用 jsonpath-plus 库
- 掌握常用的操作符和表达式
- 应用于实际开发场景
- 优化查询性能
建议:在实际项目中,将常用的 JsonPath 表达式封装为工具函数或常量,提高代码的可维护性。