匹配json字符串的正则表达式

时间:2021-10-22 15:40:11

在处理日志时, 发现文本里夹杂有json字符串, 希望能准确提取出来. 于是就有了下面这个正则表达式:

(?<json>(?:\{\s*"(?:\\"|[^"])+"\s*:\s*(?:(?P>json)|"(?:\\"|[^"])+"|[-+]?(0|[1-9]\d*)(?:\.[-+]?(0|[1-9]\d*))?(?:[eE][-+]?(0|[1-9]\d*))?|(?:true|false)|null)(?:\s*,\s*"(?:\\"|[^"])+"\s*:\s*(?:(?P>json)|"(?:\\"|[^"])+"|[-+]?(0|[1-9]\d*)(?:\.[-+]?(0|[1-9]\d*))?(?:[eE][-+]?(0|[1-9]\d*))?|(?:true|false)|null))*\s*\}|\[\s*(?:(?P>json)|"(?:\\"|[^"])+"|[-+]?(0|[1-9]\d*)(?:\.[-+]?(0|[1-9]\d*))?(?:[eE][-+]?(0|[1-9]\d*))?|(?:true|false)|null)(?:\s*,\s*(?:(?P>json)|"(?:\\"|[^"])+"|[-+]?(0|[1-9]\d*)(?:\.[-+]?(0|[1-9]\d*))?(?:[eE][-+]?(0|[1-9]\d*))?|(?:true|false)|null))*\s*\]))

匹配规则完全符合官网规范说明 http://www.json.org
生成正则时得益于两种思想: 分而治之和递归
以下是生成正则的代码和简单的测试.

<?php
function json_reg() {
echo '<xmp>';

//基础元素
$r_int = '-?\d+'; //整数: 100, -23
$r_blank = '\s*'; //空白
$r_obj_l = '\\{'.$r_blank; // {
$r_obj_r = $r_blank.'\\}'; // }
$r_arr_l = '\\['.$r_blank; // [
$r_arr_r = $r_blank.'\\]'; // [
$r_comma = $r_blank.','.$r_blank; //逗号
$r_colon = $r_blank.':'.$r_blank; //冒号

//基础数据类型
$r_str = '"(?:\\\\"|[^"])+"'; //双引号字符串
$r_num = "{$r_int}(?:\\.{$r_int})?(?:[eE]{$r_int})?"; //数字(整数,小数,科学计数): 100,-23; 12.12,-2.3; 2e9,1.2E-8
$r_bool = '(?:true|false)'; //bool值
$r_null = 'null'; //null

//衍生类型
$r_key = $r_str; //json中的key
$r_val = "(?:(?P>json)|{$r_str}|{$r_num}|{$r_bool}|{$r_null})"; //json中val: 可能为 json对象,字符串,num, bool,null
$r_kv = "{$r_key}{$r_colon}{$r_val}"; //json中的一个kv结构

$r_arr = "{$r_arr_l}{$r_val}(?:{$r_comma}{$r_val})*{$r_arr_r}"; //数组: 由val列表组成
$r_obj = "{$r_obj_l}{$r_kv}(?:{$r_comma}{$r_kv})*{$r_obj_r}"; //对象: 有kv结构组成

$reg = "/(?<json>(?:{$r_obj}|{$r_arr}))/is"; //数组或对象
echo $reg, "\n"; //最终正则表达式

//以下是测试

$str = '
{
"_in\"dex" : "log_idx",
"_type" : "test",
"_id" : "1",
"_version" : 1,
"_shards" : {
"total" : 2,
"successful" : 1.3,
"successful0" : -1.3,
"successful2" : 1.3e-3,
"successful4" : 1.3E3,
"failed" : 0
},
"created" : true
}'
;


preg_match_all($reg, $str, $arr);
print_r($arr);

}

//调用函数执行
json_reg();