概述
在JMeter性能测试中,我们经常需要处理复杂的JSON数据格式转换。本文通过一个实际案例,详细介绍如何使用JSR223后置处理器对提取的JSON数据进行格式转换,解决中文字符编码问题,并生成符合目标接口要求的数据格式。
问题场景
原始数据结构
我们通过JSON提取器获取了凭证数据,数据结构如下:
json
voucherData_ALL = [
[ // 凭证1 - 多条分录
{"vouGuid": "guid1", "amtDrYsCN": "零", ...},
{"vouGuid": "guid1", "amtDrYsCN": "零", ...}
],
[ // 凭证2 - 多条分录
{"vouGuid": "guid2", "amtDrYsCN": "零", ...}
],
// ... 更多凭证
]
目标数据结构
需要转换为以下格式,供下一个接口使用:
json
[
{
"gl_voucher_ds1": [ // 原始凭证分录数组
{"vouGuid": "guid1", "amtDrYsCN": "零", ...},
{"vouGuid": "guid1", "amtDrYsCN": "零", ...}
],
"lp_bill_info": [] // 空数组
},
{
"gl_voucher_ds1": [
{"vouGuid": "guid2", "amtDrYsCN": "零", ...}
],
"lp_bill_info": []
}
]
技术挑战
-
数据结构转换:从二维数组转换为对象数组
-
中文字符编码:防止JSON序列化时将中文字符转为Unicode编码
-
性能考虑:在JMeter中高效处理大量数据
解决方案
基础脚本实现
groovy
import groovy.json.JsonOutput
import groovy.json.JsonSlurper
// 获取提取的原始数据
def voucherDataRaw = vars.get("voucherData_ALL")
// 如果数据为空,直接返回
if (voucherDataRaw == null || voucherDataRaw == "null") {
log.error("voucherData_ALL 变量为空或未找到")
return
}
try {
// 解析JSON数据
def jsonSlurper = new JsonSlurper()
def voucherData = jsonSlurper.parseText(voucherDataRaw)
// 转换数据格式
def transformedData = []
// 遍历每个凭证
voucherData.each { voucher ->
// 创建一个新的对象
def newVoucher = [
"gl_voucher_ds1": voucher,
"lp_bill_info": []
]
// 添加到结果数组
transformedData.add(newVoucher)
}
// 将转换后的数据转换为JSON字符串
def jsonOutput = JsonOutput.toJson(transformedData)
// 打印调试信息
log.info("转换成功,共处理了 " + transformedData.size() + " 个凭证")
// 将结果存储到变量中,供下一个请求使用
vars.put("voucherData_Transformed", jsonOutput)
} catch (Exception e) {
log.error("处理凭证数据时发生错误: " + e.getMessage())
log.error("Stack trace: ", e)
}
解决中文字符编码问题
上述基础脚本存在一个问题:当使用JsonOutput.toJson()方法时,中文字符会被自动转义为Unicode编码(如:"零"变为"\u96f6")。以下是解决方案:
groovy
import groovy.json.JsonOutput
import groovy.json.JsonSlurper
// 获取提取的原始数据
def voucherDataRaw = vars.get("voucherData_ALL")
if (voucherDataRaw == null || voucherDataRaw == "null") {
log.error("voucherData_ALL 变量为空或未找到")
return
}
try {
// 解析JSON数据
def jsonSlurper = new JsonSlurper()
def voucherData = jsonSlurper.parseText(voucherDataRaw)
// 转换数据格式
def transformedData = []
// 遍历每个凭证
voucherData.each { voucher ->
// 创建一个新的对象
def newVoucher = [
"gl_voucher_ds1": voucher,
"lp_bill_info": []
]
// 添加到结果数组
transformedData.add(newVoucher)
}
// 将转换后的数据转换为JSON字符串
def jsonOutput = JsonOutput.toJson(transformedData)
// 解码Unicode转义字符,将\uXXXX格式转回中文字符
jsonOutput = jsonOutput.replaceAll(/\\u([0-9a-fA-F]{4})/) {
// 将十六进制字符串转换为字符
char c = (char) Integer.parseInt(it[1], 16)
return c.toString()
}
// 打印调试信息
log.info("转换成功,共处理了 " + transformedData.size() + " 个凭证")
// 将结果存储到变量中,供下一个请求使用
vars.put("voucherData_Transformed", jsonOutput)
} catch (Exception e) {
log.error("处理凭证数据时发生错误: " + e.getMessage())
log.error("Stack trace: ", e)
}
代码详解
1. 数据获取与解析
groovy
// 从JMeter变量获取数据
def voucherDataRaw = vars.get("voucherData_ALL")
// 使用JsonSlurper解析JSON字符串
def jsonSlurper = new JsonSlurper()
def voucherData = jsonSlurper.parseText(voucherDataRaw)
-
vars.get(): 获取JMeter变量 -
JsonSlurper: Groovy的JSON解析器,将JSON字符串转换为Groovy对象
2. 数据结构转换
groovy
def transformedData = []
voucherData.each { voucher ->
def newVoucher = [
"gl_voucher_ds1": voucher,
"lp_bill_info": []
]
transformedData.add(newVoucher)
}
-
遍历原始二维数组的每个元素(凭证)
-
为每个凭证创建新对象,包含两个字段
-
将新对象添加到结果数组
3. Unicode编码处理
groovy
jsonOutput = jsonOutput.replaceAll(/\\u([0-9a-fA-F]{4})/) {
char c = (char) Integer.parseInt(it[1], 16)
return c.toString()
}
-
使用正则表达式匹配Unicode转义序列(格式:
\uXXXX) -
将十六进制字符串转换为整数
-
将整数转换为对应的Unicode字符
-
用原字符替换Unicode转义序列
4. 结果存储与调试
groovy
// 存储结果供后续请求使用
vars.put("voucherData_Transformed", jsonOutput)
// 调试信息
log.info("转换成功,共处理了 " + transformedData.size() + " 个凭证")
性能优化建议
1. 数据量较大时的优化
groovy
// 使用StringBuilder提高字符串处理性能
def result = new StringBuilder()
def jsonOutput = JsonOutput.toJson(transformedData)
// 手动处理Unicode解码
def i = 0
while (i < jsonOutput.length()) {
if (i <= jsonOutput.length() - 6 &&
jsonOutput.charAt(i) == '\\' &&
jsonOutput.charAt(i + 1) == 'u') {
def hex = jsonOutput.substring(i + 2, i + 6)
try {
char c = (char) Integer.parseInt(hex, 16)
result.append(c)
i += 6
} catch (NumberFormatException e) {
result.append(jsonOutput.charAt(i))
i++
}
} else {
result.append(jsonOutput.charAt(i))
i++
}
}
vars.put("voucherData_Transformed", result.toString())
2. 内存使用优化
groovy
// 如果数据量非常大,考虑分批处理
def batchSize = 100
def batches = []
def currentBatch = []
voucherData.eachWithIndex { voucher, index ->
def newVoucher = [
"gl_voucher_ds1": voucher,
"lp_bill_info": []
]
currentBatch.add(newVoucher)
if (currentBatch.size() >= batchSize || index == voucherData.size() - 1) {
// 处理当前批次
def batchJson = JsonOutput.toJson(currentBatch)
batchJson = batchJson.replaceAll(/\\u([0-9a-fA-F]{4})/) {
char c = (char) Integer.parseInt(it[1], 16)
return c.toString()
}
batches.add(batchJson)
currentBatch = []
}
}
// 根据实际需求处理批次数据
// 可以将结果存储到多个变量或文件中
使用JMeter的最佳实践
1. 正确配置JSR223处理器
-
语言选择: 选择"groovy"
-
缓存编译脚本: 勾选以提高性能
-
脚本位置: 放在JSON提取器之后,目标请求之前
2. 错误处理与日志记录
groovy
// 详细的错误处理
catch (Exception e) {
log.error("处理凭证数据时发生错误: " + e.getMessage())
log.error("错误类型: " + e.getClass().getName())
// 记录原始数据用于调试
if (voucherDataRaw) {
log.error("原始数据前500字符: " + voucherDataRaw.substring(0, Math.min(500, voucherDataRaw.length())))
}
// 设置默认值或标记错误
vars.put("voucherData_Transformed", "[]")
SampleResult.setSuccessful(false)
}
3. 调试技巧
groovy
// 添加调试信息
log.info("原始数据类型: " + voucherData.getClass())
log.info("原始数据大小: " + voucherData.size())
// 检查转换结果
if (transformedData && transformedData[0].gl_voucher_ds1) {
def sampleEntry = transformedData[0].gl_voucher_ds1[0]
log.info("示例数据: amtDrYsCN = " + sampleEntry.amtDrYsCN +
", vouDesc = " + sampleEntry.vouDesc)
}
// 将结果写入文件(调试用)
def debugFile = new File("/tmp/jmeter_debug_" + System.currentTimeMillis() + ".json")
debugFile.write(jsonOutput)
log.info("调试文件已保存: " + debugFile.absolutePath)
常见问题与解决方案
问题1: 数据为空
现象 : voucherData_ALL变量为空
解决: 检查JSON提取器的配置,确保路径正确
问题2: 中文字符仍为Unicode
现象 : 转换后中文字符显示为\uXXXX格式
解决: 确保Unicode解码代码正确执行,检查正则表达式匹配
问题3: 性能问题
现象 : 处理大量数据时响应缓慢
解决:
-
启用JSR223的"缓存编译脚本"
-
使用StringBuilder代替字符串拼接
-
考虑分批处理
问题4: JSON格式错误
现象 : 解析JSON时抛出异常
解决:
-
检查原始数据格式
-
添加try-catch块进行错误处理
-
使用JSON验证工具检查数据
完整示例
以下是完整的、经过优化的脚本:
groovy
import groovy.json.JsonOutput
import groovy.json.JsonSlurper
try {
// 1. 获取原始数据
def voucherDataRaw = vars.get("voucherData_ALL")
if (!voucherDataRaw || voucherDataRaw == "null") {
log.warn("voucherData_ALL 为空,使用空数组")
vars.put("voucherData_Transformed", "[]")
return
}
// 2. 解析JSON
def jsonSlurper = new JsonSlurper()
def voucherData = jsonSlurper.parseText(voucherDataRaw)
if (!voucherData || !(voucherData instanceof List)) {
log.error("数据格式错误,期望List类型")
vars.put("voucherData_Transformed", "[]")
return
}
// 3. 转换数据结构
def transformedData = []
voucherData.each { voucher ->
transformedData.add([
"gl_voucher_ds1": voucher,
"lp_bill_info": []
])
}
// 4. 生成JSON并处理Unicode编码
def jsonOutput = JsonOutput.toJson(transformedData)
// 解码Unicode转义字符
if (jsonOutput.contains("\\u")) {
jsonOutput = jsonOutput.replaceAll(/\\u([0-9a-fA-F]{4})/) {
try {
return (char) Integer.parseInt(it[1], 16) as String
} catch (Exception e) {
return it[0] // 如果转换失败,返回原字符串
}
}
}
// 5. 存储结果
vars.put("voucherData_Transformed", jsonOutput)
// 6. 记录处理结果
log.info("数据处理完成: 转换了 " + transformedData.size() + " 个凭证")
} catch (Exception e) {
log.error("数据处理失败: " + e.getMessage())
e.printStackTrace()
// 确保有默认值
vars.put("voucherData_Transformed", "[]")
}
总结
通过本文介绍的JSR223后置处理器脚本,我们可以高效地处理JMeter中的JSON数据格式转换问题。关键点包括:
-
正确解析JSON数据:使用Groovy的JsonSlurper
-
数据结构转换:按照目标格式重组数据
-
中文字符处理:解码Unicode转义序列
-
错误处理:确保脚本健壮性
-
性能优化:使用高效的数据处理方法
这个方案不仅解决了当前的数据转换需求,也为处理其他类似的JSON数据处理场景提供了参考模板。在实际应用中,可以根据具体需求调整脚本,如添加数据验证、过滤、排序等功能。