如何解析不规范的JSON数据?
『回复列表(45|隐藏机器人聊天)』
@无名啊,有,该工具通常叫做PCRE,也就是“Perl兼容正则表达式”,它允许你自行编写规则,解析任意格式的数据。
对于 http://indexinfo.cdn.bcebos.com/BookFiles/Html/421/420161/index.html 采用
/\{"id":(\d+),"name":"?(.*?)"?,"hasContent":(\d+)\},/s
<?php
$json = file_get_contents('http://indexinfo.cdn.bcebos.com/BookFiles/Html/421/420161/index.html');
$regexp = '/\{"id":(\d+),"name":"?(.*?)"?,"hasContent":(\d+)\},/s';
preg_match_all($regexp, $json, $results, PREG_SET_ORDER);
foreach ($results as $obj) {
echo "$obj[1], $obj[2], $obj[3]\n";
}
<?php
$json = file_get_contents('http://indexinfo.cdn.bcebos.com/BookFiles/Html/545/544490/index.html');
$卷表达式 = '/\{"name":"?(.*?)"?,"list":\[(.*?)\]\},/s';
$章表达式 = '/\{"id":(\d+),"name":"?(.*?)"?,"hasContent":(\d+)\},/s';
preg_match_all($卷表达式, $json, $卷组, PREG_SET_ORDER);
foreach ($卷组 as $卷) {
echo "卷: $卷[1]\n";
preg_match_all($章表达式, $卷[2], $章组, PREG_SET_ORDER);
foreach ($章组 as $章) {
echo "章:$章[1], $章[2], $章[3]\n";
}
echo "\n";
}
@无名啊,可以使用preg_replace_callback进行修复,但依然没有通用解决方案,因为你必须扩大匹配范围,才能防止错误匹配。
/"([^"]+)":"?(.*?)"?,/
这不安全,如果标题中出现半角逗号,就会错误匹配。
/"name":"?(.*?)"?,"hasContent":/
这样才安全,通过锚定前面的name
和后面的hasContent
,才能避免匹配到错误的地方。
潜在的通用解决方案,但不成功,原因待查:
<?php
$json = file_get_contents('http://indexinfo.cdn.bcebos.com/BookFiles/Html/545/544490/index.html');
function 修复函数($arr) {
$k = $arr[1];
$v = $arr[2];
var_dump($arr);die;
$v = html_entity_decode($v); // 处理&转义
$v = preg_replace('/[\x00-\x19]/', '', $v); // 删除不允许出现的字符
$v = trim($v); // 删除前后空白
return json_encode($k).':'.json_encode($v);
}
function 修复字符串字段($json, $要修复的字段) {
$修复表达式 = '/"$要修复的字段":"?(.*?)",/s';
return preg_replace_callback($修复表达式, '修复函数', $json);
}
function 删除数组结尾逗号($json) {
return str_replace(',]', ']', $json);
}
function 修复JSON($json, $要修复的字符串字段列表) {
foreach ($要修复的字符串字段列表 as $要修复的字段) {
$json = 修复字符串字段($json, $要修复的字段);
}
$json = 删除数组结尾逗号($json);
return $json;
}
//var_dump(json_decode($json));
$json = 修复JSON($json, ['name']);
echo /*"\n\n\n",*/ $json/*, "\n\n\n"*/;
ini_set('display_errors', true);
error_reporting(E_ALL);
var_dump(json_decode($json));
<?php
$s = '
[
{"name": "第五卷:"世家"子弟"}, # 这种情况出现最多,没转义『"』
{"name": "天下大乱(上)' . chr(6) . '"}, # 出现了 ASCII 0-31 的字符,json 规范是不允许的
{"name": "\"小白兔\"少爷"}, # 转义了不该转义的字符
{"name": "番外篇~\(≧▽≦)/~"}, # 同上
{"name": "权力的Chun\药"}, # 同上
{"name": 饮马江湖"}, # 搞不明白这是怎么出现的。是标题含有“退格符”(\x08),吃掉上一个『"』吗?
{"name": style="color:Gray;"}, # ???
{"name": ""}, # 最后一项末尾不能有『,』
]
';
$has_comma = false;
$r = preg_replace_callback(
'/
(?<ws>\s+) | # 空白字符
(?<ks>[],:[{}]) | # 符号关键字
(?<cm>\#.*?(?:\n|$)) | # 注释
(?<kw>(?:true|false|null)(?=[]},\s]|$)) | # 关键字
(?<num>-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?(?=[]},\s]|$)) | # 数字
(?<str>"?(?:\\\\["\\bfnrt]?|.)*?"(?=[]},:\s]|$)) # 字符串
/sxDSu',
function ($m) use (&$has_comma) {
// 跳过 空白字符 或 注释
if (!(is_null($m['ws']) && is_null($m['cm']))) {
return '';
}
// 记录逗号,右中/大括号 前不输出逗号
elseif (!is_null($m['ks'])) {
if ($m[0] === ',') {
$has_comma = true;
}
else {
$r = ($has_comma && $m[0] !== ']' && $m[0] !== '}' ? ',' : '') . $m[0];
$has_comma = false;
return $r;
}
}
// 原样输出 关键字、数字(若记录了逗号,则先输出)
elseif (!(is_null($m['kw']) && is_null($m['num']))) {
$r = ($has_comma ? ',' : '') . $m[0];
$has_comma = false;
return $r;
}
// 都不匹配的,认为是字符串
else {
$is_begin_quoted = false;
$r = ($has_comma ? ',' : '') . '"' . preg_replace_callback(
'/(?<bg>^")|(?<esc>\\\\["\\bfnrt])|(?<bs>\\\\)|(?<qe>"$)|(?<q>")|(?<ctrl>[\x00-\x1f])/Su',
function ($m) use (&$is_begin_quoted) {
if (!is_null($m['bg'])) $is_begin_quoted = true; // 开头引号
elseif (!is_null($m['q'])) return '\\"'; // 转义 "
elseif (!is_null($m['bs'])) return '\\\\'; // 转义 \
elseif (!is_null($m['esc'])) return $m[0]; // 原样输出转义字符(暂不考虑 \uxxxx)
elseif (!is_null($m['qe'])) return $is_begin_quoted ? '' : '\\"'; // 开头若有引号,末尾的引号不认为是内容之一
elseif (!is_null($m['ctrl'])) return '【净化】'; // ASCII 0-31 的字符,可以考虑 \uxxxx 回去
},
$m[0],
flags: PREG_UNMATCHED_AS_NULL,
) . '"';
$has_comma = false;
return $r;
}
},
$s,
flags: PREG_UNMATCHED_AS_NULL,
);
echo $r . "\n";
var_dump(json_decode($r));
[{"name":"第五卷:\"世家\"子弟"},{"name":"天下大乱(上)【净化】"},{"name":"\\"小白兔\\"少爷"},{"name":"番外篇~\\(≧▽≦)/~"},{"name":"权力的Chun\\药"},{"name":"饮马江湖\""},{"name":"style=\"color:Gray;\""},{"name":""}]