[英]PHP Nested JSON from flat JSON
我有一個數據庫查詢,它為我提供了一些員工數據的輸出。 我想使用此數據傳遞給生成組織結構圖的插件。 我正在提取的JSON對象中有一些字段是:
FirstName
LastName
EmployeeID
ManagerEmployeeID
Manager Name
數據作為扁平JSON對象返回,在層次結構中員工及其經理之間沒有嵌套或關聯。
由於我無法更改源數據(數據庫查詢)的輸出,我試圖找出一種嵌套數據的方法,以便JSON輸出成為嵌套輸出。
我的目標是獲取此數組並基於ManagerID和EmployeeID將其嵌套,以便我可以創建樹層次結構。
示例數據:
• Tom Jones
o Alice Wong
o Tommy J.
• Billy Bob
o Rik A.
♣ Bob Small
♣ Small Jones
o Eric C.
我的平面數據示例:
{
"FirstName": "Tom"
"LastName": "Jones"
"EmployeeID": "123"
"ManagerEmployeeID": ""
"Manager Name": ""
},
{
"FirstName": "Alice"
"LastName": "Wong"
"EmployeeID": "456"
"ManagerEmployeeID": "123"
"Manager Name": "Tom Jones"
},
{
"FirstName": "Tommy"
"LastName": "J."
"EmployeeID": "654"
"ManagerEmployeeID": "123"
"Manager Name": "Tom Jones"
},
{
"FirstName": "Billy"
"LastName": "Bob"
"EmployeeID": "777"
"ManagerEmployeeID": ""
"Manager Name": ""
},
{
"FirstName": "Rik"
"LastName": "A."
"EmployeeID": "622"
"ManagerEmployeeID": "777"
"Manager Name": "Billy Bob"
},
{
"FirstName": "Bob"
"LastName": "Small"
"EmployeeID": "111"
"ManagerEmployeeID": "622"
"Manager Name": "Rik A."
},
{
"FirstName": "Small"
"LastName": "Jones"
"EmployeeID": "098"
"ManagerEmployeeID": "622"
"Manager Name": "Rik A"
},
{
"FirstName": "Eric"
"LastName": "C."
"EmployeeID": "222"
"ManagerEmployeeID": "777"
"Manager Name": "Billy Bob"
}
示例所需輸出:
[
{
"FirstName": "Tom",
"LastName": "Jones",
"EmployeeID": "123",
"ManagerEmployeeID": "",
"Manager Name": "",
"employees": [
{
"FirstName": "Alice",
"LastName": "Wong",
"EmployeeID": "456",
"ManagerEmployeeID": "123",
"Manager Name": "Tom Jones"
},
{
"FirstName": "Tommy",
"LastName": "J.",
"EmployeeID": "654",
"ManagerEmployeeID": "123",
"Manager Name": "Tom Jones"
}
]
},
{
"FirstName": "Billy",
"LastName": "Bob",
"EmployeeID": "777",
"ManagerEmployeeID": "",
"Manager Name": "",
"employees": [
{
"FirstName": "Rik",
"LastName": "A.",
"EmployeeID": "622",
"ManagerEmployeeID": "777",
"Manager Name": "Billy Bob",
"employees": [
{
"FirstName": "Bob",
"LastName": "Small",
"EmployeeID": "111",
"ManagerEmployeeID": "622",
"Manager Name": "Rik A."
},
{
"FirstName": "Small",
"LastName": "Jones",
"EmployeeID": "098",
"ManagerEmployeeID": "622",
"Manager Name": "Rik A"
}
]
},
{
"FirstName": "Eric",
"LastName": "C.",
"EmployeeID": "222",
"ManagerEmployeeID": "777",
"Manager Name": "Billy Bob"
}
]
}
]
實際上,我試圖使用EmployeeID
和ManagerEmployeeID
作為兩者之間的鏈接,從扁平對象創建嵌套的JSON輸出。
用PHP解決這類問題的最佳方法是什么?
賞金更新:
以下是該問題的測試用例: https : //eval.in/private/4b0635c6e7b059
您將看到名為Issue Here
最后一條記錄未顯示在結果集中。 這有一個與根節點匹配的managerID
,應該在“Tom Jones”的employees
數組中。
我有以下實用程序類來完全按照您的需要進行操作。
class NestingUtil
{
/**
* Nesting an array of records using a parent and id property to match and create a valid Tree
*
* Convert this:
* [
* 'id' => 1,
* 'parent'=> null
* ],
* [
* 'id' => 2,
* 'parent'=> 1
* ]
*
* Into this:
* [
* 'id' => 1,
* 'parent'=> null
* 'children' => [
* 'id' => 2
* 'parent' => 1,
* 'children' => []
* ]
* ]
*
* @param array $records array of records to apply the nesting
* @param string $recordPropId property to read the current record_id, e.g. 'id'
* @param string $parentPropId property to read the related parent_id, e.g. 'parent_id'
* @param string $childWrapper name of the property to place children, e.g. 'children'
* @param string $parentId optional filter to filter by parent
*
* @return array
*/
public static function nest(&$records, $recordPropId = 'id', $parentPropId = 'parent_id', $childWrapper = 'children', $parentId = null)
{
$nestedRecords = [];
foreach ($records as $index => $children) {
if (isset($children[$parentPropId]) && $children[$parentPropId] == $parentId) {
unset($records[$index]);
$children[$childWrapper] = self::nest($records, $recordPropId, $parentPropId, $childWrapper, $children[$recordPropId]);
$nestedRecords[] = $children;
}
}
return $nestedRecords;
}
}
使用您的代碼:
$employees = json_decode($flat_employees_json, true);
$managers = NestingUtil::nest($employees, 'EmployeeID', 'ManagerEmployeeID', 'employees');
print_r(json_encode($managers));
輸出:
[
{
"FirstName": "Tom",
"LastName": "Jones",
"EmployeeID": "123",
"ManagerEmployeeID": "",
"Manager Name": "",
"employees": [
{
"FirstName": "Alice",
"LastName": "Wong",
"EmployeeID": "456",
"ManagerEmployeeID": "123",
"Manager Name": "Tom Jones",
"employees": []
},
{
"FirstName": "Tommy",
"LastName": "J.",
"EmployeeID": "654",
"ManagerEmployeeID": "123",
"Manager Name": "Tom Jones",
"employees": []
}
]
},
{
"FirstName": "Billy",
"LastName": "Bob",
"EmployeeID": "777",
"ManagerEmployeeID": "",
"Manager Name": "",
"employees": [
{
"FirstName": "Rik",
"LastName": "A.",
"EmployeeID": "622",
"ManagerEmployeeID": "777",
"Manager Name": "Billy Bob",
"employees": [
{
"FirstName": "Bob",
"LastName": "Small",
"EmployeeID": "111",
"ManagerEmployeeID": "622",
"Manager Name": "Rik A.",
"employees": []
},
{
"FirstName": "Small",
"LastName": "Jones",
"EmployeeID": "098",
"ManagerEmployeeID": "622",
"Manager Name": "Rik A",
"employees": []
}
]
},
{
"FirstName": "Eric",
"LastName": "C.",
"EmployeeID": "222",
"ManagerEmployeeID": "777",
"Manager Name": "Billy Bob",
"employees": []
}
]
}
]
Edit1:修復以避免忽略某些員工
如果最后一項是具有有效經理的員工但經理不在列表中,則會被忽略,因為應該位於哪里?,它不是根,但沒有有效的經理。
為避免這種情況,請在實用程序中的return語句之前添加以下行。
if (!$parentId) {
//merge residual records with the nested array
$nestedRecords = array_merge($nestedRecords, $records);
}
return $nestedRecords;
Edit2:將實用程序更新為PHP5.6
在PHP7中進行一些測試后,該實用程序在php7.0中工作正常,但在php5.6中沒有,我不知道為什么,但是在數組引用和未設置中。 我更新實用程序代碼以使用php5.6和您的用例。
public static function nest($records, $recordPropId = 'id', $parentPropId = 'parent_id', $childWrapper = 'children', $parentId = null)
{
$nestedRecords = [];
foreach ($records as $index => $children) {
if (isset($children[$parentPropId]) && $children[$parentPropId] == $parentId) {
$children[$childWrapper] = self::nest($records, $recordPropId, $parentPropId, $childWrapper, $children[$recordPropId]);
$nestedRecords[] = $children;
}
}
if (!$parentId) {
$employeesIds = array_column($records, $recordPropId);
$managers = array_column($records, $parentPropId);
$missingManagerIds = array_filter(array_diff($managers, $employeesIds));
foreach ($records as $record) {
if (in_array($record[$parentPropId], $missingManagerIds)) {
$nestedRecords[] = $record;
}
}
}
return $nestedRecords;
}
這是從你的小提琴直接翻譯到PHP:
function makeTree($data, $parentId){
return array_reduce($data,function($r,$e)use($data,$parentId){
if(((empty($e->ManagerEmployeeID)||($e->ManagerEmployeeID==(object)[])) && empty($parentId)) or ($e->ManagerEmployeeID == $parentId)){
$employees = makeTree($data, $e->EmployeeID);
if($employees) $e->employees = $employees;
$r[] = $e;
}
return $r;
},[]);
}
它與您的測試輸入一起正常工作。 請參閱https://eval.in/private/ee9390e5e8ca95 。
用法示例:
$nested = makeTree(json_decode($json), '');
echo json_encode($nested, JSON_PRETTY_PRINT);
@rafrsr解決方案非常靈活,但問題是foreach
的unset()
。 它在迭代時修改數組,這是一個壞主意。 如果刪除unset()
,它可以正常工作。
你可以在這里使用遞歸的神奇力量。 請參考以下示例。 正如您在此處所看到的那樣,正在調用getTreeData 。
function getTreeData($data=[], $parent_key='', $self_key='', $key='')
{
if(!empty($data))
{
$new_array = array_filter($data, function($item) use($parent_key, $key) {
return $item[$parent_key] == $key;
});
foreach($new_array as &$array)
{
$array["employees"] = getTreeData($data, $parent_key, $self_key, $array[$self_key]);
if(empty($array["employees"]))
{
unset($array["employees"]);
}
}
return $new_array;
}
else
{
return $data;
}
}
$employees = json_decode($employees_json_string, true);
$employees_tree = getTreeData($employees, "ManagerEmployeeID", "EmployeeID");
這是一個巧妙的技巧,利用了對象通過引用傳遞的事實。
array_column
首先用於通過EmployeeID
重新生成輸入數組。 然后使用array_map
迭代條目並將每個項目重新分配給相應管理器的employees
數組。 由於條目是對象(stdClass) ,因此正在修改相同的輸入項。
通過引用添加到employees
數組的項目在根級別為空。 然后使用array_filter
刪除這些空條目。 最后,通過array_values
刪除臨時EmployeeID鍵控。
$input = json_decode($flat_employees_json, true);
$input = array_column($input, null, "EmployeeID");
$input = array_map(function ($entry) {
return (object) $entry;
}, $input);
$output = array_values(array_filter(array_map(function ($entry) use ($input) {
if (!empty($entry->ManagerEmployeeID)) {
$input[$entry->ManagerEmployeeID]->employees[] = $entry;
return null;
}
return $entry;
}, $input)));
echo json_encode($output, JSON_PRETTY_PRINT);
在線嘗試。
PHP 7支持array_column
的對象數組,因此輸入初始化可以簡化為:
$input = array_column(json_decode($flat_employees_json), null, "EmployeeID");
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.