我正在构建REST API,所有数据都以JSON的形式返回。每个请求都通过一个函数进行汇总,该函数负责设置HTTP状态代码,返回消息或数据,设置标题等。我还允许用户添加?fields=
参数,以指定他们想要返回的字段(例如?fields=id,hostnames,ip_addresses
),如果参数不存在,它们当然会返回所有数据。执行此操作的函数也是所提及的函数的一部分,用于设置标题/数据/消息等。我希望能够做的是允许用户使用点表示法指定字段名称,以便他们可以指定某些字段除了顶级字段。例如,我有一个这样的结构:
{
"id": "8a2b449111b449409c465c66254c6fcc",
"hostnames": [
"webapp1-sfo",
"webapp1-sfo.example.com"
],
"ip_addresses": [
"12.26.16.10",
"ee80::ae56:2dff:fd89:7868"
],
"environment": "Production",
"data_center": "sfo",
"business_unit": "Operations",
"hardware_type": "Server",
"currently_in_maintenance": false,
"history": [
{
"id": 58,
"time_start_utc": "2013-01-27 00:40:00",
"time_end_utc": "2013-01-27 01:45:00",
"ticket_number": "CHG123456",
"reason": "January production maintenance",
"links": [
{
"rel": "self",
"link": "https://localhost/api/v1/maintenances/58"
}
]
},
{
"id": 104,
"time_start_utc": "2013-02-25 14:36:00",
"time_end_utc": "2013-02-25 18:36:00",
"ticket_number": "CHG456789",
"reason": "February production maintenance",
"links": [
{
"rel": "self",
"link": "https://localhost/api/v1/maintenances/104"
}
]
},
{
"id": 143,
"time_start_utc": "2013-03-17 00:30:00",
"time_end_utc": "2013-03-17 01:55:00",
"ticket_number": "CHG789123",
"reason": "March production maintenance",
"links": [
{
"rel": "self",
"link": "https://localhost/api/v1/maintenances/143"
}
]
}
]
}
使用此功能,我可以提取顶级字段(其中$mData
是上面的数据结构,$sParams
是用户请求的字段串):
private function removeFields($mData, $sParams){
$clone = $mData; // Clone the original data
$fields = explode(',', $sParams);
// Remove fields not requested by the user
foreach($mData as $key => $value){
if(!in_array((string)$key, $fields)){
unset($mData[$key]);
}
}
// If no fields remain, restore the original data
// Chances are the user made a typo in the fields list
if(count($mData) == 0){
$mData = $clone;
}
return $mData;
}
注意:$sParams
以字符串形式出现,是用户提供的内容(以逗号分隔的字段列表)。
所以?fields=hostnames,history
会返回:
{
"hostnames": [
"webapp1-sfo",
"webapp1-sfo.example.com",
],
"history": [
{
"id": 58,
"time_start_utc": "2013-01-27 00:40:00",
"time_end_utc": "2013-01-27 01:45:00",
"ticket_number": "CHG123456",
"reason": "January production maintenance",
"links": [
{
"rel": "self",
"link": "https://localhost/api/v1/maintenances/58"
}
]
},
{
"id": 104,
"time_start_utc": "2013-02-25 14:36:00",
"time_end_utc": "2013-02-25 18:36:00",
"ticket_number": "CHG456789",
"reason": "February production maintenance",
"links": [
{
"rel": "self",
"link": "https://localhost/api/v1/maintenances/104"
}
]
},
{
"id": 143,
"time_start_utc": "2013-03-17 00:30:00",
"time_end_utc": "2013-03-17 01:55:00",
"ticket_number": "CHG789123",
"reason": "March production maintenance",
"links": [
{
"rel": "self",
"link": "https://localhost/api/v1/maintenances/143"
}
]
}
]
}
但是,如果我想从ticket_number
返回history
字段,我希望用户能够?fields=history.ticket_number
,或者如果他们想要票号和链接他们可以做这个:?fields=history.ticket_number,history.links.link
...会返回:
{
"history": [
{
"ticket_number": "CHG123456",
"links": [
{
"link": "https://localhost/api/v1/maintenances/58"
}
]
},
{
"ticket_number": "CHG456789",
"links": [
{
"link": "https://localhost/api/v1/maintenances/104"
}
]
},
{
"ticket_number": "CHG789123",
"links": [
{
"link": "https://localhost/api/v1/maintenances/143"
}
]
}
]
}
我尝试了许多不同的数组访问方法,用于堆栈溢出中的点符号,但是当history
的值是数字数组时它们都会中断...所以例如,使用我在网上找到的方法到目前为止,我需要做这样的事情来实现上面相同的输出(这显然不是很好......特别是当你有数百条记录时)。
?fields=history.0.ticket_number,history.0.links.0.link,history.1.ticket_number,history.1.links.0.link,history.2.ticket_number,history.2.links.0.link,
我也在寻找动态和递归的东西,因为每个API端点返回不同的数据结构(例如,当请求集合时,它返回一个填充了关联数组的数字数组。或者在json中,一个数组对象......以及其中一些对象可能有数组(数字或关联)。)
提前致谢
P.S。 - 我真的不在乎代码是否创建了包含所请求数据的新数据数组,或者直接操作原始数据(就像我在removeFields()函数中那样)。
更新:我已经创建了一个PHPFiddle,希望能够显示我遇到过的问题。 http://phpfiddle.org/main/code/tw1i-qu7s
答案 0 :(得分:2)
感谢您的提示和帮助。我实际上今天早上提出了一个解决方案,它似乎适用于我迄今为止测试的每个案例。它可能不是超级优雅,但适用于我需要的东西。我基本上使用点符号将数组展平为扁平数组中的键。然后我获取每个请求的字段并构建一个正则表达式(基本上用一个可选的.[digit].
替换任何“。”以捕获数字索引),然后使用正则表达式测试每个字段名称,保留匹配的字段名称。最后,我将数组重新扩展为多维数组。
扁平阵列变为:
Array
(
[id] => 8a2b449111b449409c465c66254c6fcc
[hostnames.0] => webapp1-sfo
[hostnames.1] => webapp1-sfo.example.com
[ip_addresses.0] => 12.26.16.10
[ip_addresses.1] => ee80::ae56:2dff:fd89:7868
[environment] => Production
[data_center] => sfo
[business_unit] => Operations
[hardware_type] => Server
[currently_in_maintenance] =>
[history.0.id] => 58
[history.0.time_start_utc] => 2013-01-27 00:40:00
[history.0.time_end_utc] => 2013-01-27 01:45:00
[history.0.ticket_number] => CHG123456
[history.0.reason] => January production maintenance
[history.0.links.0.rel] => self
[history.0.links.0.link] => https://localhost/api/v1/maintenances/58
[history.1.id] => 104
[history.1.time_start_utc] => 2013-02-25 14:36:00
[history.1.time_end_utc] => 2013-02-25 18:36:00
[history.1.ticket_number] => CHG456789
[history.1.reason] => February production maintenance
[history.1.links.0.rel] => self
[history.1.links.0.link] => https://localhost/api/v1/maintenances/104
[history.2.id] => 143
[history.2.time_start_utc] => 2013-03-17 00:30:00
[history.2.time_end_utc] => 2013-03-17 01:55:00
[history.2.ticket_number] => CHG789123
[history.2.reason] => March production maintenance
[history.2.links.0.rel] => self
[history.2.links.0.link] => https://localhost/api/v1/maintenances/143
)
以下是展平和展开数组的两个函数:
function flattenArray($aArrayToFlatten, $sSeparator = '.', $sParentKey = NULL){
if(!is_array($aArrayToFlatten)){
return $aArrayToFlatten;
}
$_flattened = array();
// Rewrite keys
foreach($aArrayToFlatten as $key => $value){
if($sParentKey !== NULL){
$key = $sParentKey . $sSeparator . $key;
}
$_flattened[$key] = flattenArray($value, $sSeparator, $key);
}
// Flatten
$flattened = array();
foreach($_flattened as $key => $value){
if(is_array($value)){
$flattened = array_merge($flattened, $value);
}else{
$flattened[$key] = $value;
}
}
return $flattened;
}
function expandArray($aFlattenedArray, $sSeparator = '.'){
$result = array();
foreach($aFlattenedArray as $key => $val){
$keyParts = explode($sSeparator, $key);
$currentArray = &$result;
for($i = 0; $i < count($keyParts) - 1; $i++){
if(!isset($currentArray[$keyParts[$i]])){
$currentArray[$keyParts[$i]] = array();
}
$currentArray = &$currentArray[$keyParts[$i]];
}
$currentArray[$keyParts[count($keyParts)-1]] = $val;
}
return $result;
}
示例:
$mData = json_decode('{ "id": "8a2b449111b449409c465c66254c6fcc", "hostnames": [ "webapp1-sfo", "webapp1-sfo.example.com" ], "ip_addresses": [ "12.26.16.10", "ee80::ae56:2dff:fd89:7868" ], "environment": "Production", "data_center": "sfo", "business_unit": "Operations", "hardware_type": "Server", "currently_in_maintenance": false, "history": [ { "id": 58, "time_start_utc": "2013-01-27 00:40:00", "time_end_utc": "2013-01-27 01:45:00", "ticket_number": "CHG123456", "reason": "January production maintenance", "links": [ { "rel": "self", "link": "https:\/\/localhost\/api\/v1\/maintenances\/58" } ] }, { "id": 104, "time_start_utc": "2013-02-25 14:36:00", "time_end_utc": "2013-02-25 18:36:00", "ticket_number": "CHG456789", "reason": "February production maintenance", "links": [ { "rel": "self", "link": "https:\/\/localhost\/api\/v1\/maintenances\/104" } ] }, { "id": 143, "time_start_utc": "2013-03-17 00:30:00", "time_end_utc": "2013-03-17 01:55:00", "ticket_number": "CHG789123", "reason": "March production maintenance", "links": [ { "rel": "self", "link": "https:\/\/localhost\/api\/v1\/maintenances\/143" } ] } ] }', TRUE);
print_r($mData); // Original Data
$fields = array("id", "hostnames", "history.id", "history.links.link");
$regexFields = array();
// Build regular expressions for each of the requested fields
foreach($fields as $dotNotatedFieldName){
// Requested field has a dot in it -- it's not a top-level field
// It may be part of a collection (0.fieldname.levelTwo, 1.fieldName.levelTwo,...) or be a collection (fieldName.0.levelTwo, fieldName.1.levelTwo, ...)
if(preg_match('/\./', $dotNotatedFieldName)){
$regexFields[] = "^\d*\.?" . str_replace(".", "\.\d*\.?", $dotNotatedFieldName);
// Requested field does not have a dot in it -- it's a top-level field
// It may be part of a collection (0.fieldname, 1.fieldName,...) or be a collection (fieldName.0, fieldName.1, ...)
}else{
$regexFields[] = "^\d*\.?" . $dotNotatedFieldName . "\.?\d*";
}
}
// Flatten the array
$flattened = flattenArray($mData);
// Test each flattened key against each regular expression and remove those that don't match
foreach($flattened as $key => $value){
$matchFound = FALSE;
foreach($regexFields as $regex){
if(preg_match('/' . $regex . '/', $key)){
$matchFound = TRUE;
break;
}
}
if($matchFound === FALSE){
unset($flattened[$key]);
}
}
// Expand the array
$mData = expandArray($flattened);
print_r(json_encode($mData)); // New Data
哪个输出以下JSON:
{
"id": "8a2b449111b449409c465c66254c6fcc",
"hostnames": [
"webapp1-sfo",
"webapp1-sfo.example.com"
],
"history": [
{
"id": 58,
"links": [
{
"link": "https://localhost/api/v1/maintenances/58"
}
]
},
{
"id": 104,
"links": [
{
"link": "https://localhost/api/v1/maintenances/104"
}
]
},
{
"id": 143,
"links": [
{
"link": "https://localhost/api/v1/maintenances/143"
}
]
}
]
}
答案 1 :(得分:0)
不是从克隆中删除字段,而是在字段上循环,然后将每个字段都放入克隆中
你需要一个函数让点符号遍历你的json。像这个https://selvinortiz.com/blog/traversing-arrays-using-dot-notation
private function buildResponse($mData, $sParams){
$fields = explode(',', $sParams['fields']);
$response = $mData;
if (count($fields > 0)){
$response = [];
foreach($fields as $value){
if (getDotValue($mData, $value)){
$response[$value] = $mData.getDotValue($value);
}
}
}
return json_encode($response);
}
代码未经过测试,但您可以理解我的观点。
答案 2 :(得分:0)
运行支票
if (isset($field=>$nextfield)) { //if next field level is named
//do magic here
} else if (isset($field[0])) { //if next field level is indexed
for ($i=0; $i < count($field); $i++) {
//catch 'em all
}
}
例如,history.id
会抓住if
上的history
,编号索引上的else if
,然后是if
id
}。
希望有所帮助。