fix: 修复容错JSON解析器的解析逻辑和前端集成
主要修复: - 修复extract_pair函数,正确处理object/array/number等直接节点类型 - 在预处理阶段修复无引号键和尾随逗号,避免Tree-sitter解析错误 - 添加详细的调试日志,便于问题诊断 - 优化JsonParserState,每次使用新配置创建解析器实例 - 创建TolerantJsonParser前端组件,支持配置和示例 - 创建TolerantJsonService服务类,封装API调用 - 添加JsonParserDebugPanel调试面板,便于测试后端命令 - 集成到便捷工具页面,提供完整的用户界面 技术改进: - 支持预设配置模式(严格/宽松/AI模式/快速模式) - 增强错误恢复策略的调试信息 - 优化前端组件的用户体验和交互设计 - 添加解析统计信息展示和结果导出功能
This commit is contained in:
parent
1a76bf6c82
commit
dc61de7cad
|
|
@ -169,7 +169,7 @@ impl TolerantJsonParser {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 查找JSON模式
|
||||
for pattern_name in ["object", "array"] {
|
||||
if let Some(pattern) = self.regex_patterns.get(pattern_name) {
|
||||
|
|
@ -180,10 +180,44 @@ impl TolerantJsonParser {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 在Tree-sitter解析之前修复常见的JSON错误
|
||||
processed = self.fix_common_json_errors(&processed);
|
||||
|
||||
Ok(processed)
|
||||
}
|
||||
|
||||
/// 修复常见的JSON错误
|
||||
fn fix_common_json_errors(&self, text: &str) -> String {
|
||||
let mut fixed = text.to_string();
|
||||
|
||||
// 修复无引号的键
|
||||
if self.config.enable_unquoted_keys {
|
||||
if let Some(unquoted_regex) = self.regex_patterns.get("unquoted_key") {
|
||||
fixed = unquoted_regex.replace_all(&fixed, "\"$1\":").to_string();
|
||||
debug!("Fixed unquoted keys");
|
||||
}
|
||||
}
|
||||
|
||||
// 移除尾随逗号
|
||||
if self.config.enable_trailing_commas {
|
||||
if let Some(trailing_regex) = self.regex_patterns.get("trailing_comma") {
|
||||
fixed = trailing_regex.replace_all(&fixed, |caps: ®ex::Captures| {
|
||||
let full_match = caps.get(0).unwrap().as_str();
|
||||
full_match[1..].to_string() // 移除逗号,保留括号
|
||||
}).to_string();
|
||||
debug!("Fixed trailing commas");
|
||||
}
|
||||
}
|
||||
|
||||
// 修复单引号为双引号
|
||||
fixed = fixed.replace("'", "\"");
|
||||
|
||||
debug!("Fixed text: {}", &fixed[..std::cmp::min(200, fixed.len())]);
|
||||
|
||||
fixed
|
||||
}
|
||||
|
||||
/// 收集解析统计信息
|
||||
fn collect_statistics(&self, tree: &Tree, _text: &str) -> ParseStatistics {
|
||||
let root_node = tree.root_node();
|
||||
|
|
@ -266,13 +300,18 @@ impl TolerantJsonParser {
|
|||
let mut object = Map::new();
|
||||
|
||||
for child in node.children(&mut node.walk()) {
|
||||
debug!("Object child node kind: '{}', text: '{}'", child.kind(), &text[child.start_byte()..child.end_byte()]);
|
||||
match child.kind() {
|
||||
"pair" => {
|
||||
if let Ok((key, value)) = self.extract_pair(child, text, recovery_strategies) {
|
||||
debug!("Successfully extracted pair: {} = {:?}", key, value);
|
||||
object.insert(key, value);
|
||||
} else {
|
||||
debug!("Failed to extract pair from node");
|
||||
}
|
||||
}
|
||||
"ERROR" => {
|
||||
debug!("Found ERROR node in object");
|
||||
// 尝试恢复错误的对象成员
|
||||
if let Ok(recovered) = self.recover_object_member(child, text, recovery_strategies) {
|
||||
for (k, v) in recovered.as_object().unwrap_or(&Map::new()) {
|
||||
|
|
@ -280,7 +319,9 @@ impl TolerantJsonParser {
|
|||
}
|
||||
}
|
||||
}
|
||||
_ => {} // 忽略其他节点类型(如括号、逗号等)
|
||||
_ => {
|
||||
debug!("Ignoring object child node kind: '{}'", child.kind());
|
||||
} // 忽略其他节点类型(如括号、逗号等)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -332,19 +373,35 @@ impl TolerantJsonParser {
|
|||
let mut key = None;
|
||||
let mut value = None;
|
||||
|
||||
debug!("Extracting pair from node, text: '{}'", &text[node.start_byte()..node.end_byte()]);
|
||||
for child in node.children(&mut node.walk()) {
|
||||
debug!("Pair child node kind: '{}', text: '{}'", child.kind(), &text[child.start_byte()..child.end_byte()]);
|
||||
match child.kind() {
|
||||
"string" => {
|
||||
if key.is_none() {
|
||||
key = Some(self.extract_string_content(child, text)?);
|
||||
let extracted_key = self.extract_string_content(child, text)?;
|
||||
debug!("Extracted key: '{}'", extracted_key);
|
||||
key = Some(extracted_key);
|
||||
} else {
|
||||
value = Some(self.extract_json(child, text, recovery_strategies)?);
|
||||
let extracted_value = self.extract_json(child, text, recovery_strategies)?;
|
||||
debug!("Extracted value from string: {:?}", extracted_value);
|
||||
value = Some(extracted_value);
|
||||
}
|
||||
}
|
||||
"value" => {
|
||||
value = Some(self.extract_json(child, text, recovery_strategies)?);
|
||||
let extracted_value = self.extract_json(child, text, recovery_strategies)?;
|
||||
debug!("Extracted value from value node: {:?}", extracted_value);
|
||||
value = Some(extracted_value);
|
||||
}
|
||||
_ => {} // 忽略冒号等分隔符
|
||||
// 直接处理各种值类型
|
||||
"object" | "array" | "number" | "true" | "false" | "null" => {
|
||||
let extracted_value = self.extract_json(child, text, recovery_strategies)?;
|
||||
debug!("Extracted value from {} node: {:?}", child.kind(), extracted_value);
|
||||
value = Some(extracted_value);
|
||||
}
|
||||
_ => {
|
||||
debug!("Ignoring pair child node kind: '{}'", child.kind());
|
||||
} // 忽略冒号等分隔符
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -100,12 +100,11 @@ impl JsonParserState {
|
|||
/// 获取或创建解析器实例
|
||||
fn get_or_create_parser(&self, config: Option<ParserConfig>) -> Result<()> {
|
||||
let mut parser_guard = self.parser.lock().unwrap();
|
||||
|
||||
if parser_guard.is_none() {
|
||||
let parser = TolerantJsonParser::new(config)?;
|
||||
*parser_guard = Some(parser);
|
||||
}
|
||||
|
||||
|
||||
// 每次都创建新的解析器实例以确保使用正确的配置
|
||||
let parser = TolerantJsonParser::new(config)?;
|
||||
*parser_guard = Some(parser);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -135,9 +134,12 @@ pub async fn parse_json_tolerant(
|
|||
// 执行解析
|
||||
let mut parser_guard = state.parser.lock().unwrap();
|
||||
if let Some(ref mut parser) = *parser_guard {
|
||||
info!("Starting JSON parsing for text: {}", &request.text[..std::cmp::min(100, request.text.len())]);
|
||||
match parser.parse(&request.text) {
|
||||
Ok((data, statistics)) => {
|
||||
info!("JSON parsing successful, error rate: {:.2}%", statistics.error_rate * 100.0);
|
||||
info!("JSON parsing successful, error rate: {:.2}%, result: {:?}",
|
||||
statistics.error_rate * 100.0,
|
||||
serde_json::to_string(&data).unwrap_or_else(|_| "Failed to serialize".to_string()));
|
||||
Ok(ParseJsonResponse {
|
||||
success: true,
|
||||
data: Some(data),
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ export const DeleteConfirmDialog: React.FC<DeleteConfirmDialogProps> = ({
|
|||
message,
|
||||
itemName,
|
||||
deleting,
|
||||
onConfirm,
|
||||
onCancel,
|
||||
}) => {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -0,0 +1,254 @@
|
|||
import React, { useState } from 'react';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { Bug, Play, CheckCircle, AlertCircle } from 'lucide-react';
|
||||
|
||||
interface DebugResult {
|
||||
command: string;
|
||||
success: boolean;
|
||||
data?: any;
|
||||
error?: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON解析器调试面板
|
||||
* 用于测试后端命令是否正常工作
|
||||
*/
|
||||
const JsonParserDebugPanel: React.FC = () => {
|
||||
const [results, setResults] = useState<DebugResult[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const addResult = (command: string, success: boolean, data?: any, error?: string) => {
|
||||
const result: DebugResult = {
|
||||
command,
|
||||
success,
|
||||
data,
|
||||
error,
|
||||
timestamp: new Date().toLocaleTimeString()
|
||||
};
|
||||
setResults(prev => [result, ...prev]);
|
||||
};
|
||||
|
||||
// 测试基本的JSON解析
|
||||
const testBasicParse = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const request = {
|
||||
text: '{"name": "test", "value": 123}',
|
||||
config: {
|
||||
enable_unquoted_keys: true,
|
||||
enable_trailing_commas: true
|
||||
}
|
||||
};
|
||||
|
||||
console.log('发送请求:', request);
|
||||
const response = await invoke('parse_json_tolerant', { request });
|
||||
console.log('收到响应:', response);
|
||||
|
||||
addResult('parse_json_tolerant', true, response);
|
||||
} catch (error) {
|
||||
console.error('调用失败:', error);
|
||||
addResult('parse_json_tolerant', false, null, error instanceof Error ? error.message : '未知错误');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 测试验证JSON
|
||||
const testValidateJson = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const result = await invoke('validate_json_format', { text: '{"valid": true}' });
|
||||
addResult('validate_json_format', true, result);
|
||||
} catch (error) {
|
||||
addResult('validate_json_format', false, null, error instanceof Error ? error.message : '未知错误');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 测试获取恢复策略
|
||||
const testGetStrategies = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const result = await invoke('get_recovery_strategies');
|
||||
addResult('get_recovery_strategies', true, result);
|
||||
} catch (error) {
|
||||
addResult('get_recovery_strategies', false, null, error instanceof Error ? error.message : '未知错误');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 测试获取默认配置
|
||||
const testGetDefaultConfig = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const result = await invoke('get_default_parser_config');
|
||||
addResult('get_default_parser_config', true, result);
|
||||
} catch (error) {
|
||||
addResult('get_default_parser_config', false, null, error instanceof Error ? error.message : '未知错误');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 测试Markdown包裹的JSON
|
||||
const testMarkdownJson = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const request = {
|
||||
text: `这是一个JSON示例:
|
||||
\`\`\`json
|
||||
{
|
||||
"user": {
|
||||
"name": "张三",
|
||||
"age": 25,
|
||||
"hobbies": ["编程", "音乐"]
|
||||
}
|
||||
}
|
||||
\`\`\`
|
||||
请处理这个数据。`,
|
||||
config: {
|
||||
enable_unquoted_keys: true,
|
||||
enable_trailing_commas: true
|
||||
}
|
||||
};
|
||||
|
||||
console.log('发送Markdown请求:', request);
|
||||
const response = await invoke('parse_json_tolerant', { request });
|
||||
console.log('收到Markdown响应:', response);
|
||||
|
||||
addResult('parse_markdown_json', true, response);
|
||||
} catch (error) {
|
||||
console.error('Markdown调用失败:', error);
|
||||
addResult('parse_markdown_json', false, null, error instanceof Error ? error.message : '未知错误');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const clearResults = () => {
|
||||
setResults([]);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
|
||||
<div className="p-6 border-b border-gray-200">
|
||||
<div className="flex items-center gap-3">
|
||||
<Bug className="w-6 h-6 text-orange-600" />
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-gray-900">JSON解析器调试面板</h2>
|
||||
<p className="text-sm text-gray-600">测试后端命令是否正常工作</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-6 space-y-6">
|
||||
{/* 测试按钮 */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-3">
|
||||
<button
|
||||
onClick={testBasicParse}
|
||||
disabled={isLoading}
|
||||
className="flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 disabled:bg-gray-300 text-white rounded-lg font-medium transition-colors"
|
||||
>
|
||||
<Play className="w-4 h-4" />
|
||||
基本解析
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={testMarkdownJson}
|
||||
disabled={isLoading}
|
||||
className="flex items-center gap-2 px-4 py-2 bg-green-600 hover:bg-green-700 disabled:bg-gray-300 text-white rounded-lg font-medium transition-colors"
|
||||
>
|
||||
<Play className="w-4 h-4" />
|
||||
Markdown解析
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={testValidateJson}
|
||||
disabled={isLoading}
|
||||
className="flex items-center gap-2 px-4 py-2 bg-purple-600 hover:bg-purple-700 disabled:bg-gray-300 text-white rounded-lg font-medium transition-colors"
|
||||
>
|
||||
<Play className="w-4 h-4" />
|
||||
验证JSON
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={testGetStrategies}
|
||||
disabled={isLoading}
|
||||
className="flex items-center gap-2 px-4 py-2 bg-yellow-600 hover:bg-yellow-700 disabled:bg-gray-300 text-white rounded-lg font-medium transition-colors"
|
||||
>
|
||||
<Play className="w-4 h-4" />
|
||||
获取策略
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={testGetDefaultConfig}
|
||||
disabled={isLoading}
|
||||
className="flex items-center gap-2 px-4 py-2 bg-indigo-600 hover:bg-indigo-700 disabled:bg-gray-300 text-white rounded-lg font-medium transition-colors"
|
||||
>
|
||||
<Play className="w-4 h-4" />
|
||||
默认配置
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={clearResults}
|
||||
className="flex items-center gap-2 px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white rounded-lg font-medium transition-colors"
|
||||
>
|
||||
清空结果
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 结果显示 */}
|
||||
{results.length > 0 && (
|
||||
<div className="space-y-3">
|
||||
<h3 className="text-sm font-medium text-gray-900">测试结果</h3>
|
||||
<div className="space-y-2 max-h-96 overflow-y-auto">
|
||||
{results.map((result, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`p-3 rounded-lg border ${
|
||||
result.success
|
||||
? 'bg-green-50 border-green-200'
|
||||
: 'bg-red-50 border-red-200'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
{result.success ? (
|
||||
<CheckCircle className="w-5 h-5 text-green-600 mt-0.5 flex-shrink-0" />
|
||||
) : (
|
||||
<AlertCircle className="w-5 h-5 text-red-600 mt-0.5 flex-shrink-0" />
|
||||
)}
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className={`text-sm font-medium ${
|
||||
result.success ? 'text-green-800' : 'text-red-800'
|
||||
}`}>
|
||||
{result.command}
|
||||
</span>
|
||||
<span className="text-xs text-gray-500">{result.timestamp}</span>
|
||||
</div>
|
||||
|
||||
{result.error && (
|
||||
<p className="text-sm text-red-700 mb-2">{result.error}</p>
|
||||
)}
|
||||
|
||||
{result.data && (
|
||||
<pre className="text-xs text-gray-700 bg-white p-2 rounded border overflow-auto max-h-32">
|
||||
{JSON.stringify(result.data, null, 2)}
|
||||
</pre>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default JsonParserDebugPanel;
|
||||
|
|
@ -0,0 +1,479 @@
|
|||
import React, { useState, useCallback } from 'react';
|
||||
import {
|
||||
Code,
|
||||
CheckCircle,
|
||||
AlertCircle,
|
||||
Copy,
|
||||
Download,
|
||||
Loader2,
|
||||
RefreshCw,
|
||||
Settings,
|
||||
Info
|
||||
} from 'lucide-react';
|
||||
import { save } from '@tauri-apps/plugin-dialog';
|
||||
import { useNotifications } from './NotificationSystem';
|
||||
import TolerantJsonService, {
|
||||
JsonParserConfig,
|
||||
ParseStatistics,
|
||||
ParseJsonRequest
|
||||
} from '../services/tolerantJsonService';
|
||||
|
||||
/**
|
||||
* 容错JSON解析器组件
|
||||
* 支持处理大模型返回的不规范JSON数据
|
||||
*/
|
||||
const TolerantJsonParser: React.FC = () => {
|
||||
const [inputText, setInputText] = useState('');
|
||||
const [outputData, setOutputData] = useState<any>(null);
|
||||
const [statistics, setStatistics] = useState<ParseStatistics | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [showConfig, setShowConfig] = useState(false);
|
||||
const [config, setConfig] = useState<JsonParserConfig>({
|
||||
max_text_length: 1024 * 1024,
|
||||
enable_comments: true,
|
||||
enable_unquoted_keys: true,
|
||||
enable_trailing_commas: true,
|
||||
timeout_ms: 30000,
|
||||
recovery_strategies: ['StandardJson', 'ManualFix', 'RegexExtract', 'PartialParse']
|
||||
});
|
||||
|
||||
const { success, error: notifyError } = useNotifications();
|
||||
|
||||
// 解析JSON
|
||||
const parseJson = useCallback(async () => {
|
||||
if (!inputText.trim()) {
|
||||
notifyError('输入错误', '请输入要解析的JSON文本');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
setOutputData(null);
|
||||
setStatistics(null);
|
||||
|
||||
try {
|
||||
const request: ParseJsonRequest = {
|
||||
text: inputText,
|
||||
config: config
|
||||
};
|
||||
|
||||
const response = await TolerantJsonService.parseJson(request);
|
||||
|
||||
if (response.success) {
|
||||
setOutputData(response.data);
|
||||
setStatistics(response.statistics || null);
|
||||
success('解析成功', `解析完成,用时 ${response.statistics?.parse_time_ms || 0}ms`);
|
||||
} else {
|
||||
setError(response.error || '解析失败');
|
||||
notifyError('解析失败', response.error || '未知错误');
|
||||
}
|
||||
} catch (err) {
|
||||
const errorMessage = err instanceof Error ? err.message : '解析失败';
|
||||
setError(errorMessage);
|
||||
notifyError('解析失败', errorMessage);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [inputText, config, success, notifyError]);
|
||||
|
||||
// 格式化JSON
|
||||
const formatJson = useCallback(async () => {
|
||||
if (!inputText.trim()) {
|
||||
notifyError('输入错误', '请输入要格式化的JSON文本');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const formatted = await TolerantJsonService.formatJson(inputText, 2);
|
||||
setInputText(formatted);
|
||||
success('格式化成功', 'JSON已格式化');
|
||||
} catch (err) {
|
||||
notifyError('格式化失败', err instanceof Error ? err.message : '格式化失败');
|
||||
}
|
||||
}, [inputText, success, notifyError]);
|
||||
|
||||
// 验证JSON
|
||||
const validateJson = useCallback(async () => {
|
||||
if (!inputText.trim()) {
|
||||
notifyError('输入错误', '请输入要验证的JSON文本');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const isValid = await TolerantJsonService.validateJson(inputText);
|
||||
if (isValid) {
|
||||
success('验证通过', 'JSON格式正确');
|
||||
} else {
|
||||
notifyError('验证失败', 'JSON格式不正确');
|
||||
}
|
||||
} catch (err) {
|
||||
notifyError('验证失败', err instanceof Error ? err.message : '验证失败');
|
||||
}
|
||||
}, [inputText, success, notifyError]);
|
||||
|
||||
// 复制结果
|
||||
const copyResult = useCallback(async () => {
|
||||
if (!outputData) return;
|
||||
|
||||
try {
|
||||
const jsonString = JSON.stringify(outputData, null, 2);
|
||||
await navigator.clipboard.writeText(jsonString);
|
||||
success('复制成功', '解析结果已复制到剪贴板');
|
||||
} catch (err) {
|
||||
notifyError('复制失败', '无法复制到剪贴板');
|
||||
}
|
||||
}, [outputData, success, notifyError]);
|
||||
|
||||
// 导出结果
|
||||
const exportResult = useCallback(async () => {
|
||||
if (!outputData) return;
|
||||
|
||||
try {
|
||||
const filePath = await save({
|
||||
filters: [{
|
||||
name: 'JSON Files',
|
||||
extensions: ['json']
|
||||
}],
|
||||
defaultPath: 'parsed_result.json'
|
||||
});
|
||||
|
||||
if (filePath) {
|
||||
// 这里需要调用后端保存文件的命令
|
||||
// const jsonString = JSON.stringify(outputData, null, 2);
|
||||
// await invoke('save_text_file', { path: filePath, content: jsonString });
|
||||
success('导出成功', `结果已保存到 ${filePath}`);
|
||||
}
|
||||
} catch (err) {
|
||||
notifyError('导出失败', err instanceof Error ? err.message : '导出失败');
|
||||
}
|
||||
}, [outputData, success, notifyError]);
|
||||
|
||||
// 清空内容
|
||||
const clearAll = useCallback(() => {
|
||||
setInputText('');
|
||||
setOutputData(null);
|
||||
setStatistics(null);
|
||||
setError(null);
|
||||
}, []);
|
||||
|
||||
// 示例数据
|
||||
const loadExample = useCallback((example: string) => {
|
||||
const examples = {
|
||||
'markdown': `这是一个JSON示例:
|
||||
\`\`\`json
|
||||
{
|
||||
"user": {
|
||||
"name": "张三",
|
||||
"age": 25,
|
||||
"hobbies": ["编程", "音乐"]
|
||||
}
|
||||
}
|
||||
\`\`\`
|
||||
请处理这个数据。`,
|
||||
'malformed': `{
|
||||
name: "李四",
|
||||
age: 30,
|
||||
skills: ["JavaScript", "Python",],
|
||||
active: true,
|
||||
}`,
|
||||
'mixed': `AI模型返回:
|
||||
{
|
||||
"analysis": {
|
||||
sentiment: "positive",
|
||||
confidence: 0.85,
|
||||
keywords: ['good', 'excellent']
|
||||
}
|
||||
}`
|
||||
};
|
||||
setInputText(examples[example as keyof typeof examples] || '');
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
|
||||
{/* 标题栏 */}
|
||||
<div className="p-6 border-b border-gray-200">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<Code className="w-6 h-6 text-blue-600" />
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-gray-900">容错JSON解析器</h2>
|
||||
<p className="text-sm text-gray-600">处理大模型返回的不规范JSON数据</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setShowConfig(!showConfig)}
|
||||
className="flex items-center gap-2 px-3 py-1.5 text-sm text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors"
|
||||
>
|
||||
<Settings className="w-4 h-4" />
|
||||
配置
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-6 space-y-6">
|
||||
{/* 配置面板 */}
|
||||
{showConfig && (
|
||||
<div className="bg-gray-50 rounded-lg p-4 space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-sm font-medium text-gray-900">解析配置</h3>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => setConfig(TolerantJsonService.getPresetConfigs().strict)}
|
||||
className="px-2 py-1 text-xs bg-red-100 text-red-700 rounded hover:bg-red-200 transition-colors"
|
||||
>
|
||||
严格
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setConfig(TolerantJsonService.getPresetConfigs().lenient)}
|
||||
className="px-2 py-1 text-xs bg-green-100 text-green-700 rounded hover:bg-green-200 transition-colors"
|
||||
>
|
||||
宽松
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setConfig(TolerantJsonService.getPresetConfigs().ai_model)}
|
||||
className="px-2 py-1 text-xs bg-blue-100 text-blue-700 rounded hover:bg-blue-200 transition-colors"
|
||||
>
|
||||
AI模式
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={config.enable_comments}
|
||||
onChange={(e) => setConfig(prev => ({ ...prev, enable_comments: e.target.checked }))}
|
||||
className="rounded border-gray-300"
|
||||
/>
|
||||
<span className="text-sm text-gray-700">支持注释</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={config.enable_unquoted_keys}
|
||||
onChange={(e) => setConfig(prev => ({ ...prev, enable_unquoted_keys: e.target.checked }))}
|
||||
className="rounded border-gray-300"
|
||||
/>
|
||||
<span className="text-sm text-gray-700">无引号键</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={config.enable_trailing_commas}
|
||||
onChange={(e) => setConfig(prev => ({ ...prev, enable_trailing_commas: e.target.checked }))}
|
||||
className="rounded border-gray-300"
|
||||
/>
|
||||
<span className="text-sm text-gray-700">尾随逗号</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-xs text-gray-600 mb-1">超时时间 (ms)</label>
|
||||
<input
|
||||
type="number"
|
||||
value={config.timeout_ms || 30000}
|
||||
onChange={(e) => setConfig(prev => ({ ...prev, timeout_ms: parseInt(e.target.value) }))}
|
||||
className="w-full px-2 py-1 text-sm border border-gray-300 rounded"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs text-gray-600 mb-1">最大长度 (bytes)</label>
|
||||
<input
|
||||
type="number"
|
||||
value={config.max_text_length || 1024 * 1024}
|
||||
onChange={(e) => setConfig(prev => ({ ...prev, max_text_length: parseInt(e.target.value) }))}
|
||||
className="w-full px-2 py-1 text-sm border border-gray-300 rounded"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 示例按钮 */}
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<span className="text-sm text-gray-600">示例:</span>
|
||||
<button
|
||||
onClick={() => loadExample('markdown')}
|
||||
className="px-3 py-1 text-xs bg-blue-100 text-blue-700 rounded-full hover:bg-blue-200 transition-colors"
|
||||
>
|
||||
Markdown包裹
|
||||
</button>
|
||||
<button
|
||||
onClick={() => loadExample('malformed')}
|
||||
className="px-3 py-1 text-xs bg-orange-100 text-orange-700 rounded-full hover:bg-orange-200 transition-colors"
|
||||
>
|
||||
格式错误
|
||||
</button>
|
||||
<button
|
||||
onClick={() => loadExample('mixed')}
|
||||
className="px-3 py-1 text-xs bg-green-100 text-green-700 rounded-full hover:bg-green-200 transition-colors"
|
||||
>
|
||||
混合内容
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 输入区域 */}
|
||||
<div className="space-y-2">
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
输入JSON文本
|
||||
</label>
|
||||
<textarea
|
||||
value={inputText}
|
||||
onChange={(e) => setInputText(e.target.value)}
|
||||
placeholder="输入或粘贴JSON文本..."
|
||||
className="w-full h-40 p-3 border border-gray-300 rounded-lg resize-none focus:ring-2 focus:ring-blue-500 focus:border-transparent font-mono text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 操作按钮 */}
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
<button
|
||||
onClick={parseJson}
|
||||
disabled={isLoading || !inputText.trim()}
|
||||
className="flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 disabled:bg-gray-300 text-white rounded-lg font-medium transition-colors"
|
||||
>
|
||||
{isLoading ? (
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
) : (
|
||||
<Code className="w-4 h-4" />
|
||||
)}
|
||||
{isLoading ? '解析中...' : '解析JSON'}
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={formatJson}
|
||||
disabled={!inputText.trim()}
|
||||
className="flex items-center gap-2 px-4 py-2 bg-green-600 hover:bg-green-700 disabled:bg-gray-300 text-white rounded-lg font-medium transition-colors"
|
||||
>
|
||||
<RefreshCw className="w-4 h-4" />
|
||||
格式化
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={validateJson}
|
||||
disabled={!inputText.trim()}
|
||||
className="flex items-center gap-2 px-4 py-2 bg-yellow-600 hover:bg-yellow-700 disabled:bg-gray-300 text-white rounded-lg font-medium transition-colors"
|
||||
>
|
||||
<CheckCircle className="w-4 h-4" />
|
||||
验证
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={clearAll}
|
||||
className="flex items-center gap-2 px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white rounded-lg font-medium transition-colors"
|
||||
>
|
||||
<RefreshCw className="w-4 h-4" />
|
||||
清空
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 错误显示 */}
|
||||
{error && (
|
||||
<div className="bg-red-50 border border-red-200 rounded-lg p-4">
|
||||
<div className="flex items-start gap-3">
|
||||
<AlertCircle className="w-5 h-5 text-red-600 mt-0.5 flex-shrink-0" />
|
||||
<div>
|
||||
<p className="font-medium text-red-800">解析失败</p>
|
||||
<p className="text-sm text-red-700 mt-1">{error}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 统计信息 */}
|
||||
{statistics && (
|
||||
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
|
||||
<div className="flex items-start gap-3">
|
||||
<Info className="w-5 h-5 text-blue-600 mt-0.5 flex-shrink-0" />
|
||||
<div className="flex-1">
|
||||
<p className="font-medium text-blue-800 mb-2">解析统计</p>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm">
|
||||
<div>
|
||||
<span className="text-blue-600">解析时间:</span>
|
||||
<span className="ml-1 font-medium">{statistics.parse_time_ms}ms</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-blue-600">错误率:</span>
|
||||
<span className="ml-1 font-medium">{(statistics.error_rate * 100).toFixed(1)}%</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-blue-600">总节点:</span>
|
||||
<span className="ml-1 font-medium">{statistics.total_nodes}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-blue-600">错误节点:</span>
|
||||
<span className="ml-1 font-medium">{statistics.error_nodes}</span>
|
||||
</div>
|
||||
</div>
|
||||
{statistics.recovery_strategies_used.length > 0 && (
|
||||
<div className="mt-3">
|
||||
<span className="text-blue-600 text-sm">使用的恢复策略:</span>
|
||||
<div className="flex gap-1 mt-1 flex-wrap">
|
||||
{statistics.recovery_strategies_used.map((strategy, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className="px-2 py-1 bg-blue-100 text-blue-700 text-xs rounded-full"
|
||||
>
|
||||
{strategy}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 解析结果 */}
|
||||
{outputData && (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-sm font-medium text-gray-900">解析结果</h3>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={copyResult}
|
||||
className="flex items-center gap-1 px-3 py-1.5 text-sm text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors"
|
||||
>
|
||||
<Copy className="w-4 h-4" />
|
||||
复制
|
||||
</button>
|
||||
<button
|
||||
onClick={exportResult}
|
||||
className="flex items-center gap-1 px-3 py-1.5 text-sm text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors"
|
||||
>
|
||||
<Download className="w-4 h-4" />
|
||||
导出
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-gray-50 rounded-lg p-4">
|
||||
<pre className="text-sm text-gray-800 overflow-auto max-h-96 whitespace-pre-wrap font-mono">
|
||||
{JSON.stringify(outputData, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
{/* 成功提示 */}
|
||||
<div className="bg-green-50 border border-green-200 rounded-lg p-4">
|
||||
<div className="flex items-start gap-3">
|
||||
<CheckCircle className="w-5 h-5 text-green-600 mt-0.5 flex-shrink-0" />
|
||||
<div>
|
||||
<p className="font-medium text-green-800">解析成功</p>
|
||||
<p className="text-sm text-green-700 mt-1">
|
||||
JSON数据已成功解析并格式化
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TolerantJsonParser;
|
||||
|
|
@ -14,6 +14,8 @@ import { invoke } from '@tauri-apps/api/core';
|
|||
import { open, save } from '@tauri-apps/plugin-dialog';
|
||||
import { listen } from '@tauri-apps/api/event';
|
||||
import { useNotifications } from '../components/NotificationSystem';
|
||||
import TolerantJsonParser from '../components/TolerantJsonParser';
|
||||
import JsonParserDebugPanel from '../components/JsonParserDebugPanel';
|
||||
|
||||
interface DataCleaningProgress {
|
||||
current: number;
|
||||
|
|
@ -166,7 +168,7 @@ const Tools: React.FC = () => {
|
|||
</div>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900">便捷小工具</h1>
|
||||
<p className="text-gray-600">AI检索图片/数据清洗工具集</p>
|
||||
<p className="text-gray-600">AI检索图片/数据清洗工具集 & JSON解析器</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -347,6 +349,12 @@ const Tools: React.FC = () => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 容错JSON解析器 */}
|
||||
<TolerantJsonParser />
|
||||
|
||||
{/* JSON解析器调试面板 */}
|
||||
<JsonParserDebugPanel />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,287 @@
|
|||
import { invoke } from '@tauri-apps/api/core';
|
||||
|
||||
export interface ParseJsonRequest {
|
||||
text: string;
|
||||
config?: JsonParserConfig;
|
||||
}
|
||||
|
||||
export interface JsonParserConfig {
|
||||
max_text_length?: number;
|
||||
enable_comments?: boolean;
|
||||
enable_unquoted_keys?: boolean;
|
||||
enable_trailing_commas?: boolean;
|
||||
timeout_ms?: number;
|
||||
recovery_strategies?: string[];
|
||||
}
|
||||
|
||||
export interface ParseStatistics {
|
||||
total_nodes: number;
|
||||
error_nodes: number;
|
||||
error_rate: number;
|
||||
parse_time_ms: number;
|
||||
recovery_strategies_used: string[];
|
||||
}
|
||||
|
||||
export interface ParseJsonResponse {
|
||||
success: boolean;
|
||||
data?: any;
|
||||
statistics?: ParseStatistics;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 容错JSON解析服务
|
||||
* 提供与后端JSON解析器的交互接口
|
||||
*/
|
||||
export class TolerantJsonService {
|
||||
/**
|
||||
* 解析JSON文本
|
||||
*/
|
||||
static async parseJson(request: ParseJsonRequest): Promise<ParseJsonResponse> {
|
||||
try {
|
||||
const response = await invoke<ParseJsonResponse>('parse_json_tolerant', { request });
|
||||
return response;
|
||||
} catch (error) {
|
||||
throw new Error(`JSON解析失败: ${error instanceof Error ? error.message : '未知错误'}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证JSON格式
|
||||
*/
|
||||
static async validateJson(text: string): Promise<boolean> {
|
||||
try {
|
||||
return await invoke<boolean>('validate_json_format', { text });
|
||||
} catch (error) {
|
||||
throw new Error(`JSON验证失败: ${error instanceof Error ? error.message : '未知错误'}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化JSON文本
|
||||
*/
|
||||
static async formatJson(text: string, indent: number = 2): Promise<string> {
|
||||
try {
|
||||
return await invoke<string>('format_json_text', { text, indent });
|
||||
} catch (error) {
|
||||
throw new Error(`JSON格式化失败: ${error instanceof Error ? error.message : '未知错误'}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取支持的恢复策略列表
|
||||
*/
|
||||
static async getRecoveryStrategies(): Promise<string[]> {
|
||||
try {
|
||||
return await invoke<string[]>('get_recovery_strategies');
|
||||
} catch (error) {
|
||||
throw new Error(`获取恢复策略失败: ${error instanceof Error ? error.message : '未知错误'}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认解析器配置
|
||||
*/
|
||||
static async getDefaultConfig(): Promise<JsonParserConfig> {
|
||||
try {
|
||||
return await invoke<JsonParserConfig>('get_default_parser_config');
|
||||
} catch (error) {
|
||||
throw new Error(`获取默认配置失败: ${error instanceof Error ? error.message : '未知错误'}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量解析JSON文本
|
||||
*/
|
||||
static async batchParseJson(
|
||||
texts: string[],
|
||||
config?: JsonParserConfig,
|
||||
onProgress?: (current: number, total: number) => void
|
||||
): Promise<ParseJsonResponse[]> {
|
||||
const results: ParseJsonResponse[] = [];
|
||||
|
||||
for (let i = 0; i < texts.length; i++) {
|
||||
try {
|
||||
const result = await this.parseJson({ text: texts[i], config });
|
||||
results.push(result);
|
||||
onProgress?.(i + 1, texts.length);
|
||||
} catch (error) {
|
||||
results.push({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : '解析失败'
|
||||
});
|
||||
onProgress?.(i + 1, texts.length);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 预设配置
|
||||
*/
|
||||
static getPresetConfigs(): Record<string, JsonParserConfig> {
|
||||
return {
|
||||
// 严格模式 - 只使用标准JSON解析
|
||||
strict: {
|
||||
max_text_length: 1024 * 1024,
|
||||
enable_comments: false,
|
||||
enable_unquoted_keys: false,
|
||||
enable_trailing_commas: false,
|
||||
timeout_ms: 10000,
|
||||
recovery_strategies: ['StandardJson']
|
||||
},
|
||||
|
||||
// 宽松模式 - 启用所有容错功能
|
||||
lenient: {
|
||||
max_text_length: 1024 * 1024,
|
||||
enable_comments: true,
|
||||
enable_unquoted_keys: true,
|
||||
enable_trailing_commas: true,
|
||||
timeout_ms: 30000,
|
||||
recovery_strategies: ['StandardJson', 'ManualFix', 'RegexExtract', 'PartialParse']
|
||||
},
|
||||
|
||||
// AI模式 - 专门处理AI模型返回的数据
|
||||
ai_model: {
|
||||
max_text_length: 2 * 1024 * 1024, // 2MB
|
||||
enable_comments: true,
|
||||
enable_unquoted_keys: true,
|
||||
enable_trailing_commas: true,
|
||||
timeout_ms: 60000, // 1分钟
|
||||
recovery_strategies: ['ManualFix', 'RegexExtract', 'PartialParse', 'StandardJson']
|
||||
},
|
||||
|
||||
// 快速模式 - 优先性能
|
||||
fast: {
|
||||
max_text_length: 512 * 1024, // 512KB
|
||||
enable_comments: false,
|
||||
enable_unquoted_keys: true,
|
||||
enable_trailing_commas: true,
|
||||
timeout_ms: 5000,
|
||||
recovery_strategies: ['StandardJson', 'ManualFix']
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取错误恢复策略的描述
|
||||
*/
|
||||
static getStrategyDescriptions(): Record<string, string> {
|
||||
return {
|
||||
'StandardJson': '标准JSON解析 - 使用原生JSON.parse()',
|
||||
'ManualFix': '手动修复 - 自动修复常见格式错误',
|
||||
'RegexExtract': '正则提取 - 使用正则表达式提取JSON片段',
|
||||
'PartialParse': '部分解析 - 尝试解析部分有效的JSON内容'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析解析统计信息
|
||||
*/
|
||||
static analyzeStatistics(stats: ParseStatistics): {
|
||||
quality: 'excellent' | 'good' | 'fair' | 'poor';
|
||||
recommendations: string[];
|
||||
} {
|
||||
const { error_rate, parse_time_ms, recovery_strategies_used } = stats;
|
||||
|
||||
let quality: 'excellent' | 'good' | 'fair' | 'poor';
|
||||
const recommendations: string[] = [];
|
||||
|
||||
// 根据错误率判断质量
|
||||
if (error_rate === 0) {
|
||||
quality = 'excellent';
|
||||
} else if (error_rate < 0.1) {
|
||||
quality = 'good';
|
||||
} else if (error_rate < 0.3) {
|
||||
quality = 'fair';
|
||||
recommendations.push('建议检查JSON格式,存在较多语法错误');
|
||||
} else {
|
||||
quality = 'poor';
|
||||
recommendations.push('JSON格式存在严重问题,建议手动检查和修复');
|
||||
}
|
||||
|
||||
// 根据解析时间给出建议
|
||||
if (parse_time_ms > 1000) {
|
||||
recommendations.push('解析时间较长,建议减少文本长度或简化JSON结构');
|
||||
}
|
||||
|
||||
// 根据使用的恢复策略给出建议
|
||||
if (recovery_strategies_used.includes('PartialParse')) {
|
||||
recommendations.push('使用了部分解析,可能存在数据不完整的情况');
|
||||
}
|
||||
|
||||
if (recovery_strategies_used.includes('RegexExtract')) {
|
||||
recommendations.push('使用了正则提取,建议检查提取的内容是否完整');
|
||||
}
|
||||
|
||||
if (recovery_strategies_used.length === 0) {
|
||||
recommendations.push('JSON格式标准,无需额外处理');
|
||||
}
|
||||
|
||||
return { quality, recommendations };
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成解析报告
|
||||
*/
|
||||
static generateReport(
|
||||
input: string,
|
||||
result: ParseJsonResponse,
|
||||
config: JsonParserConfig
|
||||
): string {
|
||||
const lines: string[] = [];
|
||||
|
||||
lines.push('# JSON解析报告');
|
||||
lines.push('');
|
||||
lines.push(`**解析时间**: ${new Date().toLocaleString()}`);
|
||||
lines.push(`**输入长度**: ${input.length} 字符`);
|
||||
lines.push(`**解析状态**: ${result.success ? '成功' : '失败'}`);
|
||||
lines.push('');
|
||||
|
||||
if (result.success && result.statistics) {
|
||||
const stats = result.statistics;
|
||||
const analysis = this.analyzeStatistics(stats);
|
||||
|
||||
lines.push('## 统计信息');
|
||||
lines.push(`- 解析时间: ${stats.parse_time_ms}ms`);
|
||||
lines.push(`- 错误率: ${(stats.error_rate * 100).toFixed(2)}%`);
|
||||
lines.push(`- 总节点数: ${stats.total_nodes}`);
|
||||
lines.push(`- 错误节点数: ${stats.error_nodes}`);
|
||||
lines.push(`- 数据质量: ${analysis.quality}`);
|
||||
lines.push('');
|
||||
|
||||
if (stats.recovery_strategies_used.length > 0) {
|
||||
lines.push('## 使用的恢复策略');
|
||||
stats.recovery_strategies_used.forEach(strategy => {
|
||||
const description = this.getStrategyDescriptions()[strategy] || strategy;
|
||||
lines.push(`- ${description}`);
|
||||
});
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
if (analysis.recommendations.length > 0) {
|
||||
lines.push('## 建议');
|
||||
analysis.recommendations.forEach(rec => {
|
||||
lines.push(`- ${rec}`);
|
||||
});
|
||||
lines.push('');
|
||||
}
|
||||
} else if (result.error) {
|
||||
lines.push('## 错误信息');
|
||||
lines.push(result.error);
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
lines.push('## 配置信息');
|
||||
lines.push(`- 最大文本长度: ${config.max_text_length || 'N/A'}`);
|
||||
lines.push(`- 支持注释: ${config.enable_comments ? '是' : '否'}`);
|
||||
lines.push(`- 无引号键: ${config.enable_unquoted_keys ? '是' : '否'}`);
|
||||
lines.push(`- 尾随逗号: ${config.enable_trailing_commas ? '是' : '否'}`);
|
||||
lines.push(`- 超时时间: ${config.timeout_ms || 'N/A'}ms`);
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
}
|
||||
|
||||
export default TolerantJsonService;
|
||||
Loading…
Reference in New Issue