JMeter JSON提取器实战:接口关联、多参数与数组全量提取

发布时间:2026/9/29 1:01:40
JMeter JSON提取器实战:接口关联、多参数与数组全量提取 接口测试做久了你会发现真正耗时间的往往不是发请求而是把上一个接口吐出来的数据准确地喂给下一个接口。前阵子帮一个同事看他的 JMeter 脚本场景特别典型登录成功后要拿到返回体里的用户 ID再带着这个 ID 去调用户详情接口。他先是用正则提取器抓返回体里稍微多个换行、多个转义字符就抓歪了后来又干脆写 BeanShell 脚本手动切字符串脚本调了半天一到批量压测就报错。其实这类需求JMeter 自带的 json 提取器JSON Extractor三分钟就能摆平而且它不只是能拿单个字段一次配置同时提取多个参数、把一个数组里的所有值全抓出来存成变量序列都是它的基本操作。如果你正在做接口测试、性能测试或者需要做接口之间的参数传递这篇内容应该能帮你省下不少来回试错的时间。我会从界面上的每个输入框讲起讲到单个参数提取、多个参数提取、一个参数的所有值提取再把我自己踩过的坑和排查思路一起摊开说。1. 先想清楚 json 提取器到底解决什么问题1.1 从接口关联的真实痛点说起绝大多数接口都不是孤立的。登录接口返回 token后续所有业务接口都要带上这个 token创建订单接口返回订单号查询订单接口需要这个订单号列表接口返回一批商品 ID详情接口要逐个去查。这一连串的“取上家的值喂给下家”在测试圈里叫接口关联也叫参数传递。手工测的时候你可以肉眼从响应里复制粘贴但一旦要做自动化或者压测靠手复制就完全不现实了几百上千次请求人手根本跟不上。JMeter 解决这个问题的整体思路是“后置处理器 变量”。具体说就是在一个采样器Sampler后面挂一个后置处理器让它去解析这个采样器的响应数据把需要的值抠出来存进一个变量里。之后任何地方想用这个值写${变量名}就行。json 提取器就是众多后置处理器里专门对付 JSON 响应的一种它内部用的是 JSONPath 这套表达式语言来定位数据。这里要强调一个概念json 提取器处理的是“响应体字符串”它不去管你的响应头里 Content-Type 写的是不是 application/json只要响应体本身是一段合法的 JSON 文本它就能解析。这个特性有时候是好事有些老系统返回的头不规范但内容是 JSON照样能用但反过来也意味着如果响应体前面被塞了 BOM 或者别的杂七杂八的字符解析就会直接失败这个坑后面会细说。1.2 json 提取器、正则提取器、XPath 提取器该怎么选新人最容易纠结的就是“我到底该用哪个提取器”。我的建议是接口返回是 JSON 结构优先用 json 提取器返回是 XML 或 HTML用 XPath 提取器两者都不是比如返回一段纯文本或者格式很乱的拼接串才轮到正则提取器兜底。原因很简单JSONPath 是“按结构找数据”你告诉它“去 data 下面的 token 字段”它就精确地去那个位置拿正则则是“按模式找文本”你不光要写对模式还得祈祷响应体里没有别的文本碰巧长得一样。对比项json 提取器正则提取器XPath 提取器适合的响应格式JSON任意文本XML、HTML定位方式按层级结构按字符模式按节点路径容错性结构变了会失败但不会误抓模式写松了容易抓错依赖标签结构上手难度低语法直观中正则要熟练中要懂节点语法数组全量提取原生支持很方便要用模板加匹配号稍麻烦看得出来JSON 场景下 json 提取器的优势是全方位的尤其是数组全量提取这一块后面会专门讲那才是它真正的杀手锏。正则提取器也不是没用比如响应是一段 HTML 里夹杂的 JSON 片段或者某个值藏在非标准格式的字符串中间这时候正则反而更灵活。工具没有绝对好坏看场景下菜碟。1.3 哪些场景该用哪些场景别硬上json 提取器最典型的适用场景有这么几类一是登录后拿 token、sessionId 这类凭证二是列表页拿一批 ID逐个去查详情三是创建类接口拿返回的主键供后续更新、删除使用四是拿分页信息里的总数、页码做循环控制。只要你需要从 JSON 响应里取一个或多个值它基本都能覆盖。但有几类场景我不建议硬用它。第一响应体不是 JSON你非要拿 json 提取器去解析只会得到一个空值加一堆报错这种直接换 XPath 或正则。第二你需要提取的值是由多个字段拼接、计算出来的比如“把单价和数量乘起来算总价”这不是提取器该干的活交给 JSR223 后置处理器写两行 Groovy 更合适。第三你需要跨线程组传递变量json 提取器提取出来的变量是线程私有的别的线程组读不到得配合属性Property来传。搞清楚它的能力边界比死记配置项重要得多。2. json 提取器界面字段逐个拆解2.1 五个输入框的真实含义在 JMeter 里选中一个 HTTP 请求右键 → 添加 → 后置处理器 → JSON 提取器就挂上了。界面上一共就几个输入框看着简单但每一个填错都可能让整个提取失效所以逐个说清楚。字段名作用填写要点Name of created variables提取结果的变量名多个用分号分隔顺序要和下面的表达式一一对应JSON Path expressions定位数据的 JSONPath 表达式多个用分号分隔和变量名一一对应Match No.匹配到多个结果时取哪一个0 随机、1 第一个、n 第 n 个、-1 全部Compute concatenation var是否把所有匹配值拼接起来勾选后生成一个变量名_ALL的变量Default Values匹配不到时的兜底值多个用分号分隔和变量名对应这几个字段里最容易让人犯迷糊的是 Match No. 和 Compute concatenation var后面会用整整一节展开。这里先建立一个整体印象变量名和表达式是“配对”的第几个变量名对应第几个表达式中间用分号切开多一个少一个都会错位。2.2 JSONPath 表达式够用就行的核心语法JSONPath 的语法不用全背掌握下面这些日常 95% 的提取需求都能搞定。我把它整理成一张表遇到不确定的随时查。语法含义示例$根节点$.data.键名取子节点$.data.token[键名]键名含特殊字符时用$[user-name]..键名递归查找任意层级$..token[n]数组第 n 个元素从 0 开始$.list[0].id[*]数组所有元素$.list[*].id[start:end]数组切片$.list[0:2][?(.条件)]按条件过滤$.list[?(.price100)]举个直观的例子。假设响应是这样的{ code: 200, msg: success, data: { token: abc123xyz, userId: 10086, roles: [admin, ops, dev] } }想拿 token表达式写$.data.token想拿 roles 里的第一个写$.data.roles[0]想拿 roles 全部写$.data.roles[*]同时 Match No. 设成 -1不确定 token 藏在哪一层偷懒写$..token它会把所有叫 token 的字段都找出来。这里有个新手常踩的点$..token这种递归写法如果响应里有多个同名 token它一次能匹配到多个结果这时候 Match No. 的设置就决定你最终拿到哪一个。所以递归查找虽然省事但如果数据里同名键很多反而容易抓错结构明确时老老实实写全路径更稳。2.3 变量名和表达式的对应关系错一个就全乱这是我在多个项目里反复提醒同事的一点。假设你要同时提取三个值变量名写的是token;userId;userName表达式就必须写成$.data.token;$.data.userId;$.data.userName三个对三个顺序严格对应。结果就是 token 变量存了 tokenuserId 存了 userIduserName 存了 userName。如果顺序写反了比如变量名是token;userId但表达式是$.data.userId;$.data.token那 token 变量里存的其实是 userId 的值userId 变量里存的是 token 的值。这种错位不会报错脚本照样跑但下游接口会莫名其妙地拿着错误的值去请求排查起来非常痛苦因为它“看起来是成功的”。我后来养成一个习惯提取器配好后立刻挂一个 Debug Sampler 或者用查看结果树看一眼变量值确认没错位再往下写。提示变量名尽量用有意义的英文名不要用 a、b、c 这种。脚本一多你自己都记不住哪个变量存的是什么。3. 提取单个参数以登录拿 token 为例3.1 先把接口返回和目标理清楚咱们拿一个最常见的登录场景来走一遍。登录接口返回下面这段 JSON{ code: 200, msg: 登录成功, data: { token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9, userId: 10086, expire: 7200 } }目标很明确把data.token这个值抠出来存进变量loginToken供后面的接口使用。为什么变量名叫 loginToken 而不是直接叫 token因为实际项目里可能有多个 token比如登录 token、刷新 token、第三方 token名字起得具体一点后面引用的时候一眼就能看懂。3.2 配置与验证配置步骤其实就三步。第一步在登录请求下挂 JSON 提取器。第二步Name of created variables 填loginTokenJSON Path expressions 填$.data.tokenMatch No. 保持默认的1取第一个匹配Default Values 可以不填或者填个NOT_FOUND方便排查。第三步加一个 HTTP 请求去调用户详情在请求头或者参数里引用${loginToken}。验证环节很多人会跳过导致出问题后不知道该怀疑哪一步。我的习惯是提取器配好后先在登录请求下加一个 Debug Sampler把 JMeter 变量和系统属性都勾上运行一次在查看结果树里找到 Debug Sampler 的响应检查loginToken是否等于预期值。如果这里就是空的说明提取器没配对根本不用往下查如果这里是对的后面还报错那问题就出在“引用”或者“接口依赖”上排查范围直接缩小一半。3.3 三个高频小坑第一个坑Match No. 填了 1但响应里这个字段其实是个数组。比如data下面不是对象而是数组[{token:a},{token:b}]你写$.data.token是匹配不到的因为data是数组得写$.data[0].token才对。正确的路径要严格匹配响应结构不能想当然。第二个坑token 里含有特殊字符比如点号、中括号。如果键名本身带特殊字符必须用[键名]的写法。曾经有个项目的字段叫user.name直接写$.data.user.name会被理解成三层嵌套实际它就是一个键得写$.data[user.name]。第三个坑把提取器挂错了层级。后置处理器的作用范围是“它所属的采样器”你把提取器挂在了一个事务控制器或者线程组下面可能它压根不作用于你想的那个请求。稳妥的做法是直接把提取器挂在目标 HTTP 请求的子节点下层级清晰不容易乱。4. 一次提取多个参数分号分隔的正确姿势4.1 多变量提取的配置方法实际项目里一次从响应里拿多个值才是常态。还是那个登录接口的响应这次我要同时拿 token、userId 和 expire 三个值。配置方法很直接Name of created variables 填loginToken;uid;expireTimeJSON Path expressions 依次填$.data.token;$.data.userId;$.data.expireMatch No. 保持 1Default Values 可以填;0;0这种兜底值。运行后三个变量就都有了后续请求按需引用即可。为什么要用 Default Values因为接口偶尔会返回异常结构比如失败的响应里根本没有 data 字段。这时候如果不设默认值变量就是空字符串下游引用时会拼出一个奇怪的 URL而设了默认值变量至少是个可识别的值脚本能继续跑日志里也好定位。我给默认值的习惯是字符串类型填NOT_FOUND数字类型填-1或者0这样一眼就能看出“这个是异常兜底”。4.2 Match No. 与多个变量的配合细节多变量提取时Match No. 是“统一作用”于所有表达式的。也就是说如果设成 -1每个表达式都会把所有匹配值提取出来生成变量名_1、变量名_2这样的序列而不是只对某一个变量生效。这一点很多人理解错以为可以给每个表达式单独设匹配号其实不行。如果你确实需要不同的匹配策略就得拆成多个提取器每个只管一个或一组表达式。还有个细节如果某个表达式匹配到了多个结果而 Match No. 是 1那么只取第一个不会有任何提示。所以当你发现“明明响应里有三个值变量里只有一个”先别怀疑语法去看看是不是响应结构里那个字段其实是数组或者 Match No. 被设成了 1。4.3 引用与验证引用就一句话在任何需要的地方写${uid}、${loginToken}即可URL、请求头、请求体、断言里都能用。验证的时候我推荐一个“分步验证”法先只配一个变量跑通再增加到两个跑通最后全量配上。这样如果中途出问题你能立刻知道是哪个变量引入的比一口气配五个然后大海捞针强太多。注意变量名不要和 JMeter 内置变量重名比如URL、START这类重名会带来诡异的结果虽然不一定报错但会让你调试到怀疑人生。5. 提取一个参数的所有值Match No. 等于 -1 的魔力5.1 数组全量提取的场景这是 json 提取器最值得吹的功能也是和正则提取器拉开差距的地方。假设一个商品列表接口返回这样的结构{ total: 3, items: [ {id: 1, name: 机械键盘, price: 199}, {id: 2, name: 无线鼠标, price: 89}, {id: 3, name: 显示器, price: 1299} ] }现在我想拿到所有商品的 id然后逐个去查详情。步骤是Name of created variables 填itemIdJSON Path expressions 填$.items[*].idMatch No. 填-1。运行后你会得到一组变量itemId_1、itemId_2、itemId_3分别对应三个 id另外还会自动生成一个itemId_matchNr它的值是匹配到的总数这里是 3。这三个变量的命名规律是固定的变量名_序号序号从 1 开始。itemId_matchNr这个变量特别有用它告诉你一共提取了几个配合循环控制器或者 ForEach 控制器就能做遍历。5.2 matchNr 和 _ALL 变量的区别刚才提到 Match No. 设 -1 会生成_1、_2这样的序列变量还有一个matchNr。此外如果勾选了 Compute concatenation var还会额外生成一个itemId_ALL它的值是所有匹配结果用逗号拼接成的字符串比如1,2,3。很多人分不清matchNr和_ALL该用哪个其实看需求就行。如果你要逐个处理比如循环去请求详情那用_1、_2配合matchNr做循环次数如果你只是想拿到一个逗号分隔的列表比如传给一个接口做批量查询参数那_ALL直接拿来用最省事。需要提醒的是_ALL用的逗号分隔符是 JMeter 默认的如果拼接出来的值本身包含逗号解析时可能出问题这种情况建议用序列变量自己拼。变量名含义典型用途itemId_1第一个匹配值循环中的单个值itemId_2第二个匹配值循环中的单个值itemId_matchNr匹配总数控制循环次数itemId_ALL全部值逗号拼接批量查询参数5.3 配合 ForEach 控制器做遍历光提取出来不处理是没用的实际场景往往是“提取所有 id逐个请求详情”。这时候就该 ForEach 控制器上场了。在请求外面套一个 ForEach 控制器输入变量前缀填itemId开始索引和结束索引留空输出变量名填currentId然后勾上 “Add _ before number?” 那个选项这样它就会去读itemId_1、itemId_2、itemId_3每轮循环把当前值赋给currentId。控制器里的请求参数或者路径里写${currentId}就能逐个请求详情了。我实测下来这套组合非常稳尤其是列表批量操作的压测几十上百个 id 都能按顺序走完。这里有个小坑ForEach 控制器的itemId_matchNr其实是自动帮你算的但你如果手动设了结束索引反而可能引起混乱所以索引留空让它自己判断最好。提示如果循环过程中某个 id 对应的接口报错ForEach 控制器默认会继续跑下一个不会中断。想让它停下来得配合 If 控制器或者后置断言来判断。6. 提取出来的值怎么用跨请求传递与进阶处理6.1 在 HTTP 请求里引用变量引用变量的语法就是${变量名}出现在 URL、参数、请求体、请求头里都行。要注意的是如果变量值是带空格或者特殊字符的URL 里引用可能需要编码这时候可以在请求里勾选编码选项或者用函数${__urlencode(${变量名})}处理一下。很多人遇到“接口提示参数非法”其实不是提取错了而是值里的特殊字符没编码。还有一点变量的作用域是线程私有的。同一个线程组里一个请求提取的变量后面的请求都能用但如果是不同的线程组就互相看不见。如果确实要跨线程组传得用__setProperty把变量升级成属性另一个线程组用__P读出来。6.2 和 CSV 参数化、用户定义变量的区别新人常问的一个问题是json 提取器、CSV Data Set Config、用户定义变量这三个是不是都在“传参数”到底有什么区别。我的答案是它们解决的是不同层面的问题。CSV Data Set Config 是从外部文件读一批测试数据属于“数据驱动”每个线程每轮读一行用户定义变量是脚本里手动写死的静态值属于“常量”json 提取器是从运行时响应里动态取值属于“动态提取”。三者可以组合使用比如用 CSV 提供登录账号用 json 提取器从登录响应拿 token再用用户定义变量存一些固定的环境地址。理清这个层次之后你就不会纠结“为什么我提取的变量在另一个 CSV 行里不生效”这类问题了因为提取的变量是跟着线程走的一轮请求提取一次用一次。6.3 用 JSR223 做二次加工有时候提取出来的东西不能直接用需要加工。比如前面提到的数字被读成带小数的形式或者要把多个 id 拼成特定格式的字符串这时候 JSR223 后置处理器就派上用场了。它可以直接拿上一个采样器的响应用 Groovy 解析比 json 提取器更灵活。import groovy.json.JsonSlurper // 拿到上一个采样器的响应体字符串 def resp prev.getResponseDataAsString() def json new JsonSlurper().parseText(resp) // 提取所有 id并强制转成整数字符串避免小数问题 def ids json.items.collect { it.id.toString() } // 拼成逗号分隔 vars.put(ids_all, ids.join(,)) // 取第一个 id vars.put(first_id, ids[0])这段脚本干的事和 json 提取器类似但它是“自己解析”所以对数据类型的控制更彻底。什么时候该用它当你发现 json 提取器给出的值格式不对或者需要做复杂计算、条件判断时。日常的简单提取我还是优先用 json 提取器配置化更清爽脚本写多了维护成本高。7. 常见问题与排查技巧实录7.1 提取不到值的排查速查表提取不到值是新手最头疼的问题。我把这些年高频遇到的原因整理成一张表按这个顺序排查基本都能定位到。现象可能原因排查方法变量是空字符串路径写错没匹配到用查看结果树看响应结构逐层核对路径变量是默认值同上或响应结构异常检查 Default Values 是否被触发拿了值但下游报错变量引用写错或顺序错位Debug Sampler 看变量实际值提取结果是数组表达式匹配到了多个检查 Match No. 和字段是否本就是数组某些轮次能提取某些不能响应结构不稳定看失败轮次的响应体可能返回了错误码排查的核心逻辑是“先确认响应体长什么样再确认路径对不对最后确认变量怎么用”。很多人跳过第一步凭印象写路径结果响应体里字段名早就变了自然匹配不到。我现在的习惯是每次配提取器前先把响应体完整复制到本地 JSON 工具里格式化一遍看清层级再动手。7.2 数字、中文、转义这几个特殊坑先说数字。JMeter 的 json 提取器底层用 JSONPath 实现解析在某些版本或者某些数据结构下整数类型的值被提取出来时会带上小数点比如响应里是1提取出来变成了1.0。如果你直接拿它去拼 URL 或者做数字断言就可能出错。我遇到过最典型的是用提取的 id 去查详情接口收到1.0直接报参数不合法。解决办法有两个一是用 JSR223 后置处理手动toString()时做取整二是用函数${__intSum(${变量名},0)}转一下把1.0变成1。再说中文。如果响应体是中文但显示成乱码先检查 JMeter 的编码设置看是不是文件编码和响应编码不一致。json 提取器本身不管编码它按字符串处理所以乱码问题一般出在“响应读取”或者“显示”环节去jmeter.properties里把sampleresult.default.encoding设成 UTF-8很多时候就好了。最后说转义字符。有些响应里的字符串包含了换行符\n、引号\这类转义提取出来后会保留转义形式。如果这个值要放进 JSON 请求体里通常没问题因为正好是合法转义但要是拼进 URL就得先去掉转义用 JSR223 处理一下比较稳妥。注意别在提取器里写复杂的条件逻辑提取器只负责“抠值”逻辑处理交给后置处理器或者断言职责清晰脚本才好维护。7.3 调试三板斧Debug Sampler、查看结果树、日志调试接口关联我离不开三个工具。第一是 Debug Sampler把它挂在提取器后面运行后能直接在查看结果树里看到所有 JMeter 变量和属性的当前值变量有没有提取到、值对不对一目了然。第二是查看结果树看请求响应体的原文确认响应结构。第三是 JMeter 日志遇到解析异常、路径报错这类问题日志里往往有堆栈信息比界面提示详细得多。这三板斧组合起来用基本没有查不出的问题。我一般的顺序是先看结果树确认响应结构再看 Debug Sampler 确认变量值都不对就去翻日志找异常。养成这个习惯调试效率能翻好几倍。8. 我这些年积累的实操心得配置文件写多了总会攒下一些文档里不会写、但实战中极其有用的经验。第一条提取器的 Default Values 一定要填。不填的时候变量值是空字符串下游出错时你会以为是引用问题实际是提取问题白白绕远路填了兜底值一看变量是NOT_FOUND就知道是提取没成功排查方向立刻明确。第二条变量名带上业务语义loginToken、orderId、goodsId比aaa、tmp1强太多脚本放三个月回来你自己还能看懂。第三条别在一个提取器里塞太多表达式。我见过有人一口气提十几个变量结果错位了根本看不出是哪个对哪个。超过三个变量我就倾向于拆成两个提取器各管一摊名字对得上调试也清晰。第四条提取完立刻验证别等到脚本全写完再跑那时候错误已经嵌套了好几层排查成本成倍增加。最后再分享一个我压测时的小技巧当你需要把列表接口返回的所有 id 逐个请求详情做高并发压测时用 json 提取器加 ForEach 控制器能跑通但如果列表数据量大循环次数多考虑用 JSR223 先把所有 id 一次性取出来存进一个列表再配合线程组的循环次数来分配这样比每轮都提取一次更省资源。具体用哪种看你的数据量和压测目标没有绝对的最优解只有更贴合场景的选择。