JsonPath 完全指南

0 阅读11分钟

JsonPath 完全指南

适用于 Vue 项目的新手入门与团队内部学习文档

目录


1. JsonPath 简介

1.1 什么是 JsonPath

JsonPath 是一种用于从 JSON 文档中提取数据的查询语言,类似于 XML 文档中的 XPath。它允许开发者使用简洁的表达式来定位和访问嵌套的 JSON 对象中的特定值。

1.2 JsonPath vs XPath

特性JsonPathXPath
作用对象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 函数使用注意事项

⚠️ 重要提示

  1. jsonpath-plus 限制:jsonpath-plus 库对函数的支持有限,许多函数需要通过 JavaScript 处理
  2. 推荐做法:使用 JsonPath 提取数据后,用 JavaScript 进行计算和处理
  3. 性能考虑:对于复杂计算,使用 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" &#124;&#124; @.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 运算符优先级

从高到低的运算符优先级:

  1. () - 括号(最高优先级)
  2. - (负号) ! (非)
  3. * / %
  4. + -
  5. < <= > >=
  6. == != === !==
  7. &&
  8. || (最低优先级)
// 使用括号明确优先级
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~8KBTypeScript 支持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 最佳实践

  1. 明确的路径:尽量使用明确的路径而非通配符和递归
  2. 错误处理:始终添加 try-catch 或使用包装函数
  3. 性能考虑:对大对象使用分步查询
  4. 类型检查:处理查询结果时进行类型检查
  5. 代码可读性:将复杂的 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 项目中可以大大简化数据处理逻辑。通过本文档的学习,你应该能够:

  1. 理解 JsonPath 的基本概念和语法
  2. 在 Vue 项目中正确使用 jsonpath-plus 库
  3. 掌握常用的操作符和表达式
  4. 应用于实际开发场景
  5. 优化查询性能

建议:在实际项目中,将常用的 JsonPath 表达式封装为工具函数或常量,提高代码的可维护性。