Files
EasySoft-ZenTaoPMS/lib/jsonextractor/jsonextractor.class.php
2025-10-15 11:59:30 +08:00

423 lines
14 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* JSON字段提取器
* Extract json data by fields with type conversion.
*
* 语法规则说明:
* - 简单字段field1,field2
* - 嵌套字段parent:{child1,child2}
* - 数组索引users[0]:{name,email}, users[*]:{name}
* - 字段别名field|alias, parent|parentAlias:{child|childAlias}
* - 类型转换field(类型)|alias支持类型array/int/float/bool/string
* 示例user(array)map转数组、age(int)|用户年龄(字符串转整数)
*/
class jsonextractor
{
/**
* 错误信息存储
* @var string[]
*/
private $errors = array();
/**
* 从JSON中提取字段并应用类型转换
* @param string $json JSON字符串
* @param string $query 查询字符串(支持类型转换语法)
* @return string|false 提取后的JSON字符串失败返回false
*/
public function extract($json, $query)
{
$this->errors = array();
/* 解析原始JSON */
$data = json_decode($json, true);
if(json_last_error() !== JSON_ERROR_NONE)
{
$this->errors[] = "JSON解析错误: " . json_last_error_msg();
return false;
}
/* 解析查询语句结构 */
$queryStructure = $this->parseQuery($query);
if($queryStructure === false) return false;
/* 提取字段并应用类型转换 */
$result = $this->extractFields($data, $queryStructure);
/* 返回格式化JSON */
return json_encode($result, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
}
/**
* 解析查询字符串,生成查询结构数组
* @param string $query 查询字符串
* @return array|bool 查询结构数组失败返回false
*/
private function parseQuery($query)
{
$query = trim($query);
if (empty($query)) {
$this->errors[] = "empty";
return false;
}
try
{
return $this->parseExpression($query, 0)[0];
}
catch(Exception $e)
{
$this->errors[] = "error: " . $e->getMessage();
return false;
}
}
/**
* 递归解析表达式(处理嵌套结构)
* @param string $query 查询字符串
* @param int $start 起始解析位置
* @return array [查询结构数组, 下一个解析位置]
* @throws Exception 语法错误时抛出异常
*/
private function parseExpression($query, $start)
{
$result = [];
$i = $start;
$len = strlen($query);
while($i < $len)
{
/* 跳过空白字符 */
while($i < $len && in_array($query[$i], [' ', "\t", "\n", "\r"])) $i++;
if($i >= $len) break;
/* 遇到闭合符,退出当前嵌套层级 */
if($query[$i] === '}') break;
/* 解析字段名、类型、别名、数组索引 */
$fieldInfo = $this->parseFieldName($query, $i);
$fieldName = $fieldInfo['name'];
$fieldAlias = $fieldInfo['alias'];
$arrayIndex = $fieldInfo['index'];
$targetType = $fieldInfo['target_type'];
$i = $fieldInfo['pos'];
/* 跳过空白字符 */
while($i < $len && in_array($query[$i], [' ', "\t", "\n", "\r"])) $i++;
/* 处理嵌套结构(如 parent:{child} */
if($i < $len && $query[$i] === ':')
{
$i++; // 跳过冒号
/* 跳过空白字符 */
while($i < $len && in_array($query[$i], [' ', "\t", "\n", "\r"])) $i++;
if($i < $len && $query[$i] === '{')
{
$i++; // 跳过左大括号
/* 递归解析嵌套字段 */
list($nestedResult, $newPos) = $this->parseExpression($query, $i);
$i = $newPos;
/* 检查是否有闭合符 */
if($i < $len && $query[$i] === '}')
{
$i++; // 跳过右大括号
}
else
{
throw new Exception("缺少闭合符 '}'");
}
/* 存储嵌套字段结构(包含类型转换信息)*/
$result[$fieldName] = array(
'type' => 'nested',
'alias' => $fieldAlias,
'index' => $arrayIndex,
'target_type' => $targetType,
'fields' => $nestedResult
);
}
else
{
throw new Exception("'{' afer ':'");
}
}
else
{
/* 存储简单字段结构(包含类型转换信息)*/
$result[$fieldName] = array(
'type' => 'simple',
'alias' => $fieldAlias,
'index' => $arrayIndex,
'target_type' => $targetType
);
}
/* 跳过空白字符 */
while($i < $len && in_array($query[$i], [' ', "\t", "\n", "\r"])) $i++;
/* 处理字段分隔符 */
if($i < $len && $query[$i] === ',') $i++; // 跳过逗号
}
return [$result, $i];
}
/**
* 解析字段名、类型转换、别名、数组索引
* @param string $query 查询字符串
* @param int $start 起始解析位置
* @return array 包含字段信息的数组
* @throws Exception 语法错误时抛出异常
*/
private function parseFieldName($query, $start)
{
$i = $start;
$len = strlen($query);
$name = '';
$alias = null;
$index = null;
$targetType = null;
/* 1. 读取字段名(可能包含类型转换,如 user(array)*/
while($i < $len && !in_array($query[$i], [':', ',', '}', '[', '|', ' ', "\t", "\n", "\r"]))
{
$name .= $query[$i];
$i++;
}
/* 2. 解析类型转换(格式:字段名(类型)*/
$typePattern = '/^(.+)\((\w+)\)$/';
if(preg_match($typePattern, $name, $matches))
{
$name = trim($matches[1]);
$targetType = trim($matches[2]);
/* 验证支持的类型 */
$allowedTypes = array('array', 'int', 'float', 'bool', 'string');
if(!in_array($targetType, $allowedTypes))
{
throw new Exception("Not support '{$targetType}', only " . implode(',', $allowedTypes));
}
}
/* 3. 解析别名(格式:字段名|别名)*/
if($i < $len && $query[$i] === '|')
{
$i++; // 跳过竖线
$alias = '';
while($i < $len && !in_array($query[$i], [':', ',', '}', '[', ' ', "\t", "\n", "\r"]))
{
$alias .= $query[$i];
$i++;
}
$alias = trim($alias);
/* 别名不能为空 */
if(empty($alias)) throw new Exception("alias is empty");
}
/* 4. 解析数组索引(格式:字段名[索引]*/
if($i < $len && $query[$i] === '[')
{
$i++; // 跳过左中括号
$indexStr = '';
while($i < $len && $query[$i] !== ']')
{
$indexStr .= $query[$i];
$i++;
}
/* 检查是否有闭合符 */
if($i < $len && $query[$i] === ']')
{
$i++; // 跳过右中括号
/* 处理通配符索引和数字索引 */
if($indexStr === '*')
{
$index = '*';
}
elseif(is_numeric($indexStr))
{
$index = intval($indexStr);
}
else
{
throw new Exception("index error '{$indexStr}', only support number or '*'");
}
}
else
{
throw new Exception("need ']'");
}
}
/* 字段名不能为空 */
if(empty($name)) throw new Exception("field is empty");
return array(
'name' => $name,
'alias' => $alias,
'index' => $index,
'target_type' => $targetType,
'pos' => $i
);
}
/**
* 提取字段并应用类型转换
* @param array|object $data 原始数据(数组或对象)
* @param array $queryStructure 查询结构数组
* @return array 提取并转换后的结果数组
*/
private function extractFields($data, $queryStructure)
{
/* 非数组结构直接返回(避免后续报错)*/
if(!is_array($queryStructure)) return $this->convertValueType($data, null);
if(!is_array($data) && !is_object($data)) return null;
$result = array();
$dataArray = (array)$data;
foreach($queryStructure as $field => $config)
{
$resultKey = $config['alias'] ?? $field; // 优先使用别名
$targetType = $config['target_type'] ?? null; // 目标转换类型
/* 1. 处理通配符字段(如 *:{name}*/
if($field === '*')
{
foreach($dataArray as $key => $value)
{
if($config['type'] === 'simple')
{
/* 简单通配符字段:直接转换值 */
$result[$key] = $this->convertValueType($value, $targetType);
}
elseif($config['type'] === 'nested')
{
/* 嵌套通配符字段:先提取嵌套字段,再转换 */
$nestedValue = $this->extractFields($value, $config['fields']);
$result[$key] = $this->convertValueType($nestedValue, $targetType);
}
}
continue;
}
/* 字段不存在时跳过 */
if(!array_key_exists($field, $dataArray)) continue;
$fieldData = $dataArray[$field];
/* 2. 处理数组索引(如 users[0]、users[*]*/
if($config['index'] !== null)
{
/* 非数组类型跳过 */
if(!is_array($fieldData)) continue;
/* 2.1 通配符索引(提取所有数组元素)*/
if($config['index'] === '*')
{
$extractedArray = array();
foreach($fieldData as $item)
{
/* 提取元素值(简单/嵌套)*/
$itemValue = $config['type'] === 'simple'
? $item
: $this->extractFields($item, $config['fields']);
/* 应用类型转换 */
$extractedArray[] = $this->convertValueType($itemValue, $targetType);
}
$result[$resultKey] = $extractedArray;
}
/* 2.2 特定数字索引(提取指定位置元素)*/
elseif(isset($fieldData[$config['index']]))
{
$item = $fieldData[$config['index']];
/* 提取元素值(简单/嵌套)*/
$itemValue = $config['type'] === 'simple'
? $item
: $this->extractFields($item, $config['fields']);
/* 应用类型转换并保留索引*/
$result[$resultKey] = array(
$config['index'] => $this->convertValueType($itemValue, $targetType)
);
}
}
/* 3. 无数组索引(普通字段)*/
else
{
/* 提取字段值(简单/嵌套)*/
$fieldValue = $config['type'] === 'simple'
? $fieldData
: $this->extractFields($fieldData, $config['fields']);
/* 应用类型转换 */
$result[$resultKey] = $this->convertValueType($fieldValue, $targetType);
}
}
return $result;
}
/**
* 按目标类型转换值
* @param mixed $value 原始值
* @param string|null $targetType 目标类型array/int/float/bool/string
* @return mixed 转换后的值
*/
private function convertValueType($value, ?string $targetType)
{
/* 无目标类型时返回原始值 */
if($targetType === null) return $value;
switch($targetType)
{
/* 转换为数组:关联数组→索引数组,非数组→包装为数组 */
case 'array':
if(is_array($value)) return array_values($value);
return [$value];
/* 转换为整数:仅对数值/数值字符串有效 */
case 'int':
return is_numeric($value) ? intval($value) : $value;
/* 转换为浮点数:仅对数值/数值字符串有效 */
case 'float':
return is_numeric($value) ? floatval($value) : $value;
/* 转换为布尔值:处理字符串"true"/"false"和数字1/0 */
case 'bool':
if(is_string($value))
{
$lowerValue = strtolower($value);
return $lowerValue === 'true' || $lowerValue === '1';
}
return boolval($value);
/* 转换为字符串:直接强制转换 */
case 'string':
return strval($value);
/* 未知类型返回原始值 */
default:
return $value;
}
}
/**
* 获取错误信息列表
* @return string[] 错误信息数组
*/
public function getErrors()
{
return $this->errors;
}
}