I have an array that looks like the following:
[
'applicant' => [
'user' => [
'username' => true,
'password' => true,
'data' => [
'value' => true,
'anotherValue' => true
]
]
]
]
What I want to be able to do is convert that array into an array that looks like:
[
'applicant.user.username',
'applicant.user.password',
'applicant.user.data.value',
'applicant.user.data.anotherValue'
]
Basically, I need to somehow loop through the nested array and every time a leaf node is reached, save the entire path to that node as a dot separated string.
Only keys with true as a value are leaf nodes, every other node will always be an array. How would I go about accomplishing this?
edit
This is what I have tried so far, but doesnt give the intended results:
$tree = $this->getTree(); // Returns the above nested array
$crumbs = [];
$recurse = function ($tree, &$currentTree = []) use (&$recurse, &$crumbs)
{
foreach ($tree as $branch => $value)
{
if (is_array($value))
{
$currentTree[] = $branch;
$recurse($value, $currentTree);
}
else
{
$crumbs[] = implode('.', $currentTree);
}
}
};
$recurse($tree);
This function does what you want:
function flattenArray($arr) {
$output = [];
foreach ($arr as $key => $value) {
if (is_array($value)) {
foreach(flattenArray($value) as $flattenKey => $flattenValue) {
$output["${key}.${flattenKey}"] = $flattenValue;
}
} else {
$output[$key] = $value;
}
}
return $output;
}
You can see it running here.
Related
I want to transform an array of resources that can have an infinity of children to a simple array like below. I just want to keep the information of the parent, if there is a parent. In my context, a parent is the array just above the child array.
I have this array (bigger in reality with a lof of children), but each children may have an infinity of arrays children:
$array = array (
0 =>
array (
'#id' => 'Authorization',
'#sortOrder' => '1',
'resource' =>
array (
'#id' => 'Authorization2',
'#title' => 'Authorization2',
),
),
);
And I would like to get this, recursively :
$resources = [
0 => [
'parent' => null,
'resource' => 'Authorization'],
1 => [
'Authorization' => 'Authorization',
'resource' => 'Authorization2']
];
I tried this and I get every single resource but I can't get parents for resources that has one:
public function array_values_recursive($array) {
$flat = array();
foreach($array as $key => $value) {
if (is_array($value)) {
$flat = array_merge($flat, $this->array_values_recursive($value));
}
else {
if($key === '#id') {
$flat[]['value'] = $value;
}
}
}
return $flat;
}
That did the job for me, thanks #Sammitch for the idea.
public function array_values_recursive($array, $parent = null) {
$flat = array();
$i = 0;
foreach($array as $key => $value) {
if (is_array($value)) {
//we create a new parent
if(array_key_exists('#id',$array)){
$flat = array_merge($flat, $this->array_values_recursive($value, $array['#id']));
}
//we keep the last parent known
else{
$flat = array_merge($flat, $this->array_values_recursive($value, $parent));
}
}
else {
if($key === '#id') {
if($parent){
$flat[$i]['value'] = $value;
$flat[$i]['parent'] = $parent;
}
else{
$flat[$i]['value'] = $value;
}
$i++;
}
}
}
return $flat;
}
I have a multidimensional array that can have any depth. What im trying to do is to filter the whole path based on dynamic keys and create a new array of it.
Example of the array
$originalArray = [
"title" => "BACKPACK MULTICOLOUR",
"description" => "description here",
"images" => [
[
"id" => 12323123123,
"width" => 635,
"height" => 560,
"src" => "https://example.com",
"variant_ids": [32694976315473, 32863017926737],
],
[
"id" => 4365656656565,
"width" => 635,
"height" => 560,
"src" => "https://example.com",
"variant_ids": [32694976315473, 32863017926737],
]
],
"price" => [
"normal" => 11.00,
"discount" => [
"gold_members" => 9.00,
"silver_members" => 10.00,
"bronze_members" => null
]
]
];
Example how the output should look like with the key "title, width, height, gold_members" filtered out. Only keys from the end of the array tree should be valid, so nothing must happen when images is in the filter
$newArray = [
"title" => "BACKPACK MULTICOLOUR",
"images" => [
[
"width" => 635,
"height" => 560,
],
[
"width" => 635,
"height" => 560,
]
],
"price" => [
"discount" => [
"gold_members" => 9.00,
]
]
];
I guess that i should create a function that loop through each element and when it is an associative array, it should call itself again
Because the filtered paths are unknown i cannot make a hardcoded setter like this:
$newArray["images"][0]["width"] = 635
The following filter will be an example but it should basically be dynamic
example what i have now:
$newArray = handleArray($originalArray);
handleArray($array)
{
$filter = ["title", "width", "height", "gold_members"];
foreach ($array as $key => $value) {
if (is_array($value)) {
$this->handleArray($value);
} else {
if (in_array($key, $filter)) {
// put this full path in the new array
}
}
}
}
[Solved] Update:
I solved my problem thanks to #trincot
I used his code and added an extra check to add an array with multiple values to the new array
My code to solve the issue:
<?php
function isListOfValues($array) {
$listOfArrays = [];
foreach ($array as $key => $value) {
$listOfArrays[] = ! is_array($value) && is_int($key);
}
return array_sum($listOfArrays) === count($listOfArrays);
}
function filterKeysRecursive(&$arr, &$keep) {
$result = [];
foreach ($arr as $key => $value) {
if (is_array($value) && ! isListOfValues($value)) {
$value = filterKeysRecursive($value, $keep);
if (count($value)) {
$result[$key] = $value;
}
} else if (array_key_exists($key, $keep)) {
$result[$key] = $value;
}
}
return $result;
}
$keep = array_flip(["title", "width", "height", "gold_members"]);
$result = filterKeysRecursive($originalArray, $keep);
You could use a recursive function, with following logic:
base case: the value associated with a key is not an array (it is a "leaf"). In that case the new object will have that key/value only when the key is in the list of desired keys.
recursive case: the value associated with a key is an array. Apply recursion to that value. Only add the key when the returned result is not an empty array. In that case associate the filtered value to the key in the result object.
To speed up the look up in the list of keys, it is better to flip that list into an associative array.
Here is the implementation:
function filter_keys_recursive(&$arr, &$keep) {
foreach ($arr as $key => $value) {
if (is_array($value)) {
$value = filter_keys_recursive($value, $keep);
if (count($value)) $result[$key] = $value;
} else if (array_key_exists($key, $keep)) {
$result[$key] = $value;
}
}
return $result;
}
$originalArray = ["title" => "BACKPACK MULTICOLOUR","description" => "description here","images" => [["id" => 12323123123,"width" => 635,"height" => 560,"src" => "https://example.com"],["id" => 4365656656565,"width" => 635,"height" => 560,"src" => "https://example.com"]],"price" => ["normal" => 11.00,"discount" => ["gold_members" => 9.00,"silver_members" => 10.00,"bronze_members" => null]]];
$keep = array_flip(["title", "width", "height", "gold_members"]);
$result = filter_keys_recursive($originalArray, $keep);
My proposition to you is to write a custom function to transform structure from one schema to another:
function transform(array $originalArray): array {
array_walk($originalArray['images'], function (&$a, $k) {
unset($a['id']); unset($a['src']);
});
unset($originalArray['description']);
unset($originalArray['price']['normal']);
unset($originalArray['price']['discount']['silver_members']);
unset($originalArray['price']['discount']['bronze_members']);
return $originalArray;
}
var_dump(transform($originalArray));
If you are familiar with OOP I suggest you to look at how DTO works in API Platform for example and inject this idea into your code by creating custom DataTransformers where you specify which kind of structers you want to support with transformer and a method where you transform one structure to another.
Iterate over the array recursively on each key and subarray.
If the current key in the foreach is a required key in the result then:
If the value is not an array, simply assign the value
If the value is an array, iterate further down over value recursively just in case if there is any other filtering of the subarray keys that needs to be done.
If the current key in the foreach is NOT a required key in the result then:
Iterate over value recursively if it's an array in itself. This is required because there could be one of the filter keys deep down which we would need. Get the result and only include it in the current subresult if it's result is not an empty array. Else, we can skip it safely as there are no required keys down that line.
Snippet:
<?php
function filterKeys($array, $filter_keys) {
$sub_result = [];
foreach ($array as $key => $value) {
if(in_array($key, $filter_keys)){// if $key itself is present in $filter_keys
if(!is_array($value)) $sub_result[$key] = $value;
else{
$temp = filterKeys($value, $filter_keys);
$sub_result[$key] = count($temp) > 0 ? $temp : $value;
}
}else if(is_array($value)){// if $key is not present in $filter_keys - iterate over the remaining subarray for that key
$temp = filterKeys($value, $filter_keys);
if(count($temp) > 0) $sub_result[$key] = $temp;
}
}
return $sub_result;
}
$result = filterKeys($originalArray, ["title", "width", "height", "gold_members"]);
print_r($result);
Online Demo
Try this way.
$expectedKeys = ['title','images','width','height','price','gold_members'];
function removeUnexpectedKeys ($originalArray,$expectedKeys)
{
foreach ($originalArray as $key=>$value) {
if(is_array($value)) {
$originalArray[$key] = removeUnexpectedKeys($value,$expectedKeys);
if(!is_array($originalArray[$key]) or count($originalArray[$key]) == 0) {
unset($originalArray[$key]);
}
} else {
if (!in_array($key,$expectedKeys)){
unset($originalArray[$key]);
}
}
}
return $originalArray;
}
$newArray = removeUnexpectedKeys ($originalArray,$expectedKeys);
print_r($newArray);
check this on editor,
https://www.online-ide.com/vFN69waXMf
I have an array that looks like the following:
[
'applicant' => [
'user' => [
'username' => true,
'password' => true,
'data' => [
'value' => true,
'anotherValue' => true
]
]
]
]
What I want to be able to do is convert that array into an array that looks like:
[
'applicant.user.username',
'applicant.user.password',
'applicant.user.data.value',
'applicant.user.data.anotherValue'
]
Basically, I need to somehow loop through the nested array and every time a leaf node is reached, save the entire path to that node as a dot separated string.
Only keys with true as a value are leaf nodes, every other node will always be an array. How would I go about accomplishing this?
edit
This is what I have tried so far, but doesnt give the intended results:
$tree = $this->getTree(); // Returns the above nested array
$crumbs = [];
$recurse = function ($tree, &$currentTree = []) use (&$recurse, &$crumbs)
{
foreach ($tree as $branch => $value)
{
if (is_array($value))
{
$currentTree[] = $branch;
$recurse($value, $currentTree);
}
else
{
$crumbs[] = implode('.', $currentTree);
}
}
};
$recurse($tree);
This function does what you want:
function flattenArray($arr) {
$output = [];
foreach ($arr as $key => $value) {
if (is_array($value)) {
foreach(flattenArray($value) as $flattenKey => $flattenValue) {
$output["${key}.${flattenKey}"] = $flattenValue;
}
} else {
$output[$key] = $value;
}
}
return $output;
}
You can see it running here.
This seems simple, but has perplexed me. I need to get $env to look like $desired result.
I tried using explode and foreach loops in a multitude of ways but keep getting stuck.
$env = [
["mysql_user"=>"user var"],
["mysql_pass"=>"password var"],
["rabbit_list_one"=>"listone var"],
["rabbit_list_two"=>"listtwo var"],
["system_var_main_deep"=>"deep this"],
["system_var_main_that"=>"deep that"]
];
$desiredResult = [
"mysql" => [
"user" => "user var",
"pass" => "password var"
],
"rabbit" => [
"list" => [
"one" => "listone var",
"two" => "listtwo var"
]
],
"system" => [
"var" => [
"main" => [
"deep" => "deep this",
"that" => "deep that"
]
]
]
];
Double-check the formatting on $env, because you're showing arrays inside of the $env array rather than just key/value pairs. Assuming your input is correct, and there are actually inner arrays, this should work:
$out = [];
foreach ($env as $piece) {
foreach ($piece as $key => $value) {
$key_full = explode('_', $key);
$key_last = array_pop($key_full);
$pointer = &$out;
foreach ($key_full as $key_level) {
if (!isset($pointer[$key_level])) {
$pointer[$key_level] = [];
}
$pointer = &$pointer[$key_level];
}
$pointer[$key_last] = $value;
}
}
Based on Netrilix's answer, this was the final solution. This handled the situation where FOO_BAR_BAX and FOO_BAR were both set. We opted to always take the array over the string. Thank you all for all your help!
public function build() {
$config = $_ENV;
$out = [];
foreach ($config as $key => $value) {
$key_full = explode('_', $key);
$key_last = strtolower(array_pop($key_full));
$pointer = &$out;
foreach ($key_full as $key_level) {
$key_level = strtolower($key_level);
if (!isset($pointer[$key_level])) {
$pointer[$key_level] = [];
}
$pointer = &$pointer[$key_level];
}
$pointer = !is_array($pointer) ? [] : $pointer;
if (!isset($pointer[$key_last])) {
$pointer[$key_last] = $value;
}
}
return $out;
}
Okay, so I need to dynamically dig into a JSON structure with PHP and I don't even know if it is possible.
So, let's say that my JSON is stored ad the variable $data:
$data = {
'actions':{
'bla': 'value_actionBla',
'blo': 'value_actionBlo',
}
}
So, to access the value of value_actionsBla, I just do $data['actions']['bla']. Simple enough.
My JSON is dynamically generated, and the next time, it is like this:
$data = {
'actions':{
'bla': 'value_actionBla',
'blo': 'value_actionBlo',
'bli':{
'new': 'value_new'
}
}
}
Once again, to get the new_value, I do: $data['actions']['bli']['new'].
I guess you see the issue.
If I need to dig two levels, then I need to write $data['first_level']['second_level'], with three, it will be $data['first_level']['second_level']['third_level'] and so on ...
Is there any way to perform such actions dynamically? (given I know the keys)
EDIT_0: Here is an example of how I do it so far (in a not dynamic way, with 2 levels)
// For example, assert that 'value_actionsBla' == $data['actions']['bla']
foreach($data as $expected => $value) {
$this->assertEquals($expected, $data[$value[0]][$value[1]]);
}
EDIT_1
I have made a recursive function to do it, based on the solution of #Matei Mihai:
private function _isValueWhereItSupposedToBe($supposedPlace, $value, $data){
foreach ($supposedPlace as $index => $item) {
if(($data = $data[$item]) == $value)
return true;
if(is_array($item))
$this->_isValueWhereItSupposedToBe($item, $value, $data);
}
return false;
}
public function testValue(){
$searched = 'Found';
$data = array(
'actions' => array(
'abort' => '/abort',
'next' => '/next'
),
'form' => array(
'title' => 'Found'
)
);
$this->assertTrue($this->_isValueWhereItSupposedToBe(array('form', 'title'), $searched, $data));
}
You can use a recursive function:
function array_search_by_key_recursive($needle, $haystack)
{
foreach ($haystack as $key => $value) {
if ($key === $needle) {
return $value;
}
if (is_array($value) && ($result = array_search_by_key_recursive($needle, $value)) !== false) {
return $result;
}
}
return false;
}
$arr = ['test' => 'test', 'test1' => ['test2' => 'test2']];
var_dump(array_search_by_key_recursive('test2', $arr));
The result is string(5) "test2"
You could use a function like this to traverse down an array recursively (given you know all the keys for the value you want to access!):
function array_get_nested_value($data, array $keys) {
if (empty($keys)) {
return $data;
}
$current = array_shift($keys);
if (!is_array($data) || !isset($data[$current])) {
// key does not exist or $data does not contain an array
// you could also throw an exception here
return null;
}
return array_get_nested_value($data[$current], $keys);
}
Use it like this:
$array = [
'test1' => [
'foo' => [
'hello' => 123
]
],
'test2' => 'bar'
];
array_get_nested_value($array, ['test1', 'foo', 'hello']); // will return 123