JsonSanitizer
JsonSanitizer 是面向大模型输出的类 JSON 容错工具。它可以将工具参数中常见的 JavaScript 风格值转换为 JSON 字符串,从夹带说明文字的输出中提取对象,并补齐缺失的对象花括号。
import com.agentsflex.core.util.JsonSanitizer;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
String raw = "{\"formatter\": function(v) { return v * 2; }}";
String sanitized = JsonSanitizer.sanitize(raw);
JSONObject arguments = JSON.parseObject(sanitized);
// arguments.getString("formatter")
// -> function(v) { return v * 2; }该类位于 agents-flex-core 模块的 com.agentsflex.core.util 包中,不需要额外依赖。
1. 适用场景
大模型生成 Tool Calling 参数时,可能返回以下非标准 JSON 内容:
参数如下:{
"status": success,
"formatter": (value) => ({ result: value }),
"createdAt": new Date(),
"pattern": /foo{1,3}/gi,
"fallback": undefined
}直接交给严格 JSON 解析器通常会失败。JsonSanitizer 负责在解析前做有限、可预测的文本修复,适合:
- Tool Calling 参数容错。
- 从带 Markdown 或说明文字的模型输出中提取对象。
- 保留 JavaScript 表达式源码供业务后续识别或展示。
- 补齐流式输出截断造成的外层对象花括号缺失。
WARNING
JsonSanitizer 只修复文本,不验证字段类型、必填项、数值范围或调用权限。修复后仍须使用 JSON 解析器解析,并执行正常的业务校验。
2. sanitize
sanitize(String text) 扫描类 JSON 文本,将已识别的非 JSON 值写成合法的 JSON 字符串值。
String raw = "{"
+ "\"name\": 'demo',"
+ "\"status\": success,"
+ "\"enabled\": true,"
+ "\"count\": 3,"
+ "\"empty\": null"
+ "}";
JSONObject result = JSON.parseObject(JsonSanitizer.sanitize(raw));
result.getString("name"); // demo
result.getString("status"); // success
result.getBoolean("enabled"); // true
result.getInteger("count"); // 3
result.get("empty"); // null当前支持的转换包括:
| 输入形式 | 转换结果 |
|---|---|
单引号字符串,如 'demo' | 标准双引号 JSON 字符串 |
function (...) {...}、async function (...) {...} | 保留完整源码的字符串 |
箭头函数,如 (v) => ({value: v}) | 保留完整源码的字符串 |
new Date() 等 new 表达式 | 保留完整源码的字符串 |
正则字面量,如 /foo[,}]bar/gi | 保留完整源码的字符串 |
undefined、NaN、Infinity | 对应源码字符串 |
冒号后的普通未加引号值,如 status: success | 字符串 "success" |
标准 JSON 的 true、false、null 和数值会保持原有类型。已经位于双引号字符串内部的 function、new 或正则样式文本也不会被再次转换。
String raw = "{\"code\":\"function() { return new Date(); }\"}";
String sanitized = JsonSanitizer.sanitize(raw);
// sanitized 与 raw 保持一致表达式中的嵌套函数、对象、数组、字符串、模板字符串、注释和常见正则字面量会按字符层级扫描,内部逗号或花括号不会被误认为当前 JSON 值的结束位置。
空值行为
- 参数为
null时返回null。 - 参数为空字符串时返回空字符串。
- 方法返回的是待解析文本,不保证结果一定能被 JSON 解析器接受。
3. extractObjects
extractObjects(String text) 从混合文本中提取所有花括号配对完整的对象候选,并按出现顺序返回。
String output = "候选一:{\"id\":1},候选二:{\"id\":2}";
List<String> candidates = JsonSanitizer.extractObjects(output);
// ["{\"id\":1}", "{\"id\":2}"]扫描会忽略双引号字符串、单引号字符串、模板字符串、JavaScript 注释和正则字面量内部的花括号:
String output = "说明 {\"pattern\": /a{1,3}/g, \"text\": \"}\"} 结束";
List<String> candidates = JsonSanitizer.extractObjects(output);
JSONObject value = JSON.parseObject(
JsonSanitizer.sanitize(candidates.get(0))
);需要注意:
- 只返回花括号完整配对的对象;未闭合对象不会出现在结果中。
- 没有候选、输入为
null或空字符串时返回空列表。 - 返回的是文本候选,不代表候选一定是合法 JSON。
- 文本中存在多个对象时,应由业务根据协议选择,而不是默认信任第一个对象。
4. completeObject
completeObject(String text) 用于补齐缺失的外层左花括号和未闭合的右花括号。
String missingStart = "\"name\":\"demo\",\"enabled\":true}";
String completedStart = JsonSanitizer.completeObject(missingStart);
// {"name":"demo","enabled":true}
String missingEnd = "{\"id\":42,\"options\":{\"debug\":true";
String completedEnd = JsonSanitizer.completeObject(missingEnd);
// {"id":42,"options":{"debug":true}}该方法按实际未闭合的对象层数追加右花括号,并忽略字符串、注释和正则字面量内部的花括号。它不会补全缺失的引号、方括号、冒号或逗号,也不会删除多余的结构符号。
输入为 null 或空字符串时原样返回。
5. 组合容错流程
处理不受信任的模型文本时,建议优先尝试原始 JSON,再逐步扩大容错范围:
static JSONObject parseModelObject(String raw) {
try {
return JSON.parseObject(raw);
} catch (RuntimeException ignored) {
// 继续尝试有限容错
}
try {
return JSON.parseObject(JsonSanitizer.sanitize(raw));
} catch (RuntimeException ignored) {
// 尝试从说明文字中提取完整对象
}
for (String candidate : JsonSanitizer.extractObjects(raw)) {
try {
return JSON.parseObject(JsonSanitizer.sanitize(candidate));
} catch (RuntimeException ignored) {
// 继续尝试下一个候选
}
}
String completed = JsonSanitizer.completeObject(raw);
return JSON.parseObject(JsonSanitizer.sanitize(completed));
}先解析原始 JSON 可以避免对合法输入做不必要的转换;后续每一步只针对一种常见模型输出偏差。
6. 与 ToolCall 的集成
通常无需手动清洗框架收到的 Tool Calling 参数。ToolCall.getArgsMap() 已内置以下解析顺序:
- 直接解析原始参数。
- 调用
sanitize()后解析。 - 使用
extractObjects()提取候选,分别尝试原始候选和清洗后的候选。 - 使用
completeObject()补齐对象,再尝试原始和清洗后的内容。
ToolCall toolCall = new ToolCall();
toolCall.setArguments(
"参数如下:{\"status\": success, \"pattern\": /abc/g}"
);
Map<String, Object> arguments = toolCall.getArgsMap();
arguments.get("status"); // success
arguments.get("pattern"); // /abc/g直接使用 JsonSanitizer 更适合自定义模型协议、非 ToolCall 的结构化输出,或者需要自行控制候选选择策略的场景。
7. 安全与边界
JsonSanitizer 不执行任何 JavaScript。函数、箭头函数、正则和 new 表达式只会作为普通字符串保留。但调用方仍应遵守以下约束:
- 不要将保留下来的表达式交给脚本引擎执行。
- 不要把“能够解析”视为参数可信;仍要校验字段白名单、类型、长度、范围和权限。
- 对多个对象候选建立明确选择协议,避免模型前缀中的示例对象被误当成实际参数。
- 对输入大小设置上限,避免对不受控的超长模型输出执行重复解析。
- 该工具不是完整的 JavaScript 词法分析器,只覆盖工具参数中常见且能够确定边界的语法。