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
given a nested array of arbitrary depth like this:
$array = array(
1400=>
array(7300=>
array(
7301=> array(),
7302=> array(),
7305=> array(
7306=>array()
),
),
7314=>array()
),
);
how would one get the hierarchy of keys for any key.
for example:
getkeys(7305);
should return 1400,7300,7305 in that order
or
getkeys(7314);
should return 1400,7314
all array keys are unique values
Using RecursiveIteratorIterator
$array = array(
1400 => array(
7300 => array(
7301=> array(),
7302 => array(),
7305 => array(
7306=>array()
),
),
7314=>array()
),
);
function getKeys($key, $array) {
$found_path = [];
$ritit = new RecursiveIteratorIterator(new RecursiveArrayIterator($array), RecursiveIteratorIterator::SELF_FIRST);
foreach ($ritit as $leafValue) {
$path = array();
foreach (range(0, $ritit->getDepth()) as $depth) {
$path[] = $ritit->getSubIterator($depth)->key();
}
if (end($path) == $key) {
$found_path = $path;
break;
}
}
return $found_path;
}
print_r(getKeys(7305, $array));
// Array
// (
// [0] => 1400
// [1] => 7300
// [2] => 7305
// )
This is very interesting problem you have so I tried to make a function that will echo your keys. If this is not good enough pls let me know I can improve code. Thanks.
<?php
$a = array(
1400=>
array(7300=>
array(
7301=> array(),
7302=> array(),
7305=> array(
7306=>array()
),
),
7314=>array()
),
);
$mykey = 7306;
$level = 0;
$result = array();
$resultarray = test($a,$mykey,$level,$result);
function test($array,$mykey,$level,$result){
$level++;
foreach($array as $key => $element){
if($key == $mykey){
echo 'found';
print_r($result);
exit;
} else if(is_array($element)){
$result[$level] = $key;
$result1 = test($element,$mykey,$level,$result);
}
}
}
The idea is to check current array branch, and if the needle key isn't found, then iterate current items and check their array child nodes by recursive function calls. Before each step down we push a current key to stack, and pop the stack if the function does not found a needle key in whole branch. So if the key found, the function returns true by the chain, preserving successful keys in the stack.
function branchTraversing(& $branch, & $key_stack, $needle_key) {
$found = false;
if (!array_key_exists($needle_key, $branch)) {
reset($branch);
while (!$found && (list($key, $next_branch) = each($branch))) {
if (is_array($next_branch)) {
array_push($key_stack, $key);
$found = branchTraversing($next_branch, $key_stack, $needle_key);
if (!$found) {
array_pop($key_stack);
}
}
}
} else {
array_push($key_stack, $needle_key);
$found = true;
}
return $found;
}
function getPath(& $array, $needle_key) {
$path = [];
branchTraversing($array, $path, $needle_key);
return $path;
}
$test_keys = [1400, 7300, 7302, 7306, 7314, 666];
foreach ($test_keys as $search_key) {
echo '<p>' . $search_key . ' => [ '
. implode(', ', getPath($array, $search_key)) . ' ]</p>';
}
I am trying to convert a multidimensional array into a string.
Till now I have been able to convert a pipe delimited string into an array.
Such as:
group|key|value
group|key_second|value
Will render into the following array:
$x = array(
'group' => array(
'key' => 'value',
'key_second' => 'value'
),
);
However, now I want it to be the other way around, where a multidimensional array is provided and I want to convert it to a pipe delimited string just like in the first code example.
Any ideas how to do this ?
PS: Please do note that the array can dynamically have any depth.
For example:
$x['group']['sub_group']['category']['key'] = 'value'
Translates to
group|sub_group|category|key|value
I have created my own function:
This should have no problem handling even big arrays
function array_to_pipe($array, $delimeter = '|', $parents = array(), $recursive = false)
{
$result = '';
foreach ($array as $key => $value) {
$group = $parents;
array_push($group, $key);
// check if value is an array
if (is_array($value)) {
if ($merge = array_to_pipe($value, $delimeter, $group, true)) {
$result = $result . $merge;
}
continue;
}
// check if parent is defined
if (!empty($parents)) {
$result = $result . PHP_EOL . implode($delimeter, $group) . $delimeter . $value;
continue;
}
$result = $result . PHP_EOL . $key . $delimeter . $value;
}
// somehow the function outputs a new line at the beginning, we fix that
// by removing the first new line character
if (!$recursive) {
$result = substr($result, 1);
}
return $result;
}
Demo provided here http://ideone.com/j6nThF
You can also do this using a loop like this:
$x = array(
'group' => array(
'key' => 'value',
'key_second' => 'value'
)
);
$yourstring ="";
foreach ($x as $key => $value)
{
foreach ($x[$key] as $key2 => $value2)
{
$yourstring .= $key.'|'.$key2.'|'.$x[$key][$key2]."<BR />";
}
}
echo $yourstring;
Here is a working DEMO
This code should do the thing.
You needed a recursive function to do this. But be careful not to pass object or a huge array into it, as this method is very memory consuming.
function reconvert($array,$del,$path=array()){
$string="";
foreach($array as $key=>$val){
if(is_string($val) || is_numeric($val)){
$string.=implode($del,$path).$del.$key.$del.$val."\n";
} else if(is_bool($val)){
$string.=implode($del,$path).$del.$key.$del.($val?"True":"False")."\n";
} else if(is_null($val)){
$string.=implode($del,$path).$del.$key.$del."NULL\n";
}else if(is_array($val)=='array') {
$path[]=$key;
$string.=reconvert($val,$del,$path);
array_pop($path);
} else {
throw new Exception($key." has type ".gettype($val).' which is not a printable value.');
}
}
return $string;
}
DEMO: http://ideone.com/89yLLo
You can do it by
Look at serialize and unserialize.
Look at json_encode and json_decode
Look at implode
And Possible duplicate of Multidimensional Array to String
You can do this if you specifically want a string :
$x = array(
'group' => array(
'key' => 'value',
'key_second' => 'value'
),
'group2' => array(
'key2' => 'value',
'key_second2' => 'value'
),
);
$str='';
foreach ($x as $key=>$value)
{
if($str=='')
$str.=$key;
else
$str.="|$key";
foreach ($value as $key1=>$value1)
$str.="|$key1|$value1";
}
echo $str; //it will print group|key|value|key_second|value|group2|key2|value|key_second2|value
array_key_exists is not working for large multidimensional array. For ex
$arr = array(
'1' => 10,
'2' => array(
'21' => 21,
'22' => 22,
'23' => array(
'test' => 100,
'231' => 231
),
),
'3' => 30,
'4' => 40
);
array_key_exists('test',$arr) returns 'false' but it works with some simple arrays.
array_key_exists does NOT work recursive (as Matti Virkkunen already pointed out). Have a look at the PHP manual, there is the following piece of code you can use to perform a recursive search:
<?php
function array_key_exists_r($needle, $haystack)
{
$result = array_key_exists($needle, $haystack);
if ($result) return $result;
foreach ($haystack as $v) {
if (is_array($v)) {
$result = array_key_exists_r($needle, $v);
}
if ($result) return $result;
}
return $result;
}
array_key_exists doesn't work on multidimensionaml arrays. if you want to do so, you have to write your own function like this:
function array_key_exists_multi($n, $arr) {
foreach ($arr as $key=>$val) {
if ($n===$key) {
return $key;
}
if (is_array($val)) {
if(multi_array_key_exists($n, $val)) {
return $key . ":" . array_key_exists_multi($n, $val);
}
}
}
return false;
}
this returns false if the key isn't found or a string containing the "location" of the key in that array (like 2:23:test) if it's found.
$test_found = false;
array_walk_recursive($arr,
function($v, $k) use (&$test_found)
{
$test_found |= ($k == 'test');
});
This requires PHP 5.3 or later.
Here is another one, works on any dimension array
function findValByKey($arr , $keySearch){
$out = null;
if (is_array($arr)){
if (array_key_exists($keySearch, $arr)){
$out = $arr[$keySearch];
}else{
foreach ($arr as $key => $value){
if ($out = self::findValByKey($value, $keySearch)){
break;
}
}
}
}
return $out;
}
Is there any fast way to get all subarrays where a key value pair was found in a multidimensional array? I can't say how deep the array will be.
Simple example array:
$arr = array(0 => array(id=>1,name=>"cat 1"),
1 => array(id=>2,name=>"cat 2"),
2 => array(id=>3,name=>"cat 1")
);
When I search for key=name and value="cat 1" the function should return:
array(0 => array(id=>1,name=>"cat 1"),
1 => array(id=>3,name=>"cat 1")
);
I guess the function has to be recursive to get down to the deepest level.
Code:
function search($array, $key, $value)
{
$results = array();
if (is_array($array)) {
if (isset($array[$key]) && $array[$key] == $value) {
$results[] = $array;
}
foreach ($array as $subarray) {
$results = array_merge($results, search($subarray, $key, $value));
}
}
return $results;
}
$arr = array(0 => array(id=>1,name=>"cat 1"),
1 => array(id=>2,name=>"cat 2"),
2 => array(id=>3,name=>"cat 1"));
print_r(search($arr, 'name', 'cat 1'));
Output:
Array
(
[0] => Array
(
[id] => 1
[name] => cat 1
)
[1] => Array
(
[id] => 3
[name] => cat 1
)
)
If efficiency is important you could write it so all the recursive calls store their results in the same temporary $results array rather than merging arrays together, like so:
function search($array, $key, $value)
{
$results = array();
search_r($array, $key, $value, $results);
return $results;
}
function search_r($array, $key, $value, &$results)
{
if (!is_array($array)) {
return;
}
if (isset($array[$key]) && $array[$key] == $value) {
$results[] = $array;
}
foreach ($array as $subarray) {
search_r($subarray, $key, $value, $results);
}
}
The key there is that search_r takes its fourth parameter by reference rather than by value; the ampersand & is crucial.
FYI: If you have an older version of PHP then you have to specify the pass-by-reference part in the call to search_r rather than in its declaration. That is, the last line becomes search_r($subarray, $key, $value, &$results).
How about the SPL version instead? It'll save you some typing:
// I changed your input example to make it harder and
// to show it works at lower depths:
$arr = array(0 => array('id'=>1,'name'=>"cat 1"),
1 => array(array('id'=>3,'name'=>"cat 1")),
2 => array('id'=>2,'name'=>"cat 2")
);
//here's the code:
$arrIt = new RecursiveIteratorIterator(new RecursiveArrayIterator($arr));
foreach ($arrIt as $sub) {
$subArray = $arrIt->getSubIterator();
if ($subArray['name'] === 'cat 1') {
$outputArray[] = iterator_to_array($subArray);
}
}
What's great is that basically the same code will iterate through a directory for you, by using a RecursiveDirectoryIterator instead of a RecursiveArrayIterator. SPL is the roxor.
The only bummer about SPL is that it's badly documented on the web. But several PHP books go into some useful detail, particularly Pro PHP; and you can probably google for more info, too.
<?php
$arr = array(0 => array("id"=>1,"name"=>"cat 1"),
1 => array("id"=>2,"name"=>"cat 2"),
2 => array("id"=>3,"name"=>"cat 1")
);
$arr = array_filter($arr, function($ar) {
return ($ar['name'] == 'cat 1');
//return ($ar['name'] == 'cat 1' AND $ar['id'] == '3');// you can add multiple conditions
});
echo "<pre>";
print_r($arr);
?>
Ref: http://php.net/manual/en/function.array-filter.php
Came back to post this update for anyone needing an optimisation tip on these answers, particulary John Kugelman's great answer up above.
His posted function work fine but I had to optimize this scenario for handling a 12 000 row resultset. The function was taking an eternal 8 secs to go through all records, waaaaaay too long.
I simply needed the function to STOP searching and return when match was found. Ie, if searching for a customer_id, we know we only have one in the resultset and once we find the customer_id in
the multidimensional array, we want to return.
Here is the speed-optimised ( and much simplified ) version of this function, for anyone in need. Unlike other version, it can only handle only one depth of array, does not recurse and does away with merging multiple results.
// search array for specific key = value
public function searchSubArray(Array $array, $key, $value) {
foreach ($array as $subarray){
if (isset($subarray[$key]) && $subarray[$key] == $value)
return $subarray;
}
}
This brought down the the task to match the 12 000 records to a 1.5 secs. Still very costly but much more reasonable.
if (isset($array[$key]) && $array[$key] == $value)
A minor imporvement to the fast version.
Here is solution:
<?php
$students['e1003']['birthplace'] = ("Mandaluyong <br>");
$students['ter1003']['birthplace'] = ("San Juan <br>");
$students['fgg1003']['birthplace'] = ("Quezon City <br>");
$students['bdf1003']['birthplace'] = ("Manila <br>");
$key = array_search('Delata Jona', array_column($students, 'name'));
echo $key;
?>
Be careful of linear search algorithms (the above are linear) in multiple dimensional arrays as they have compounded complexity as its depth increases the number of iterations required to traverse the entire array. Eg:
array(
[0] => array ([0] => something, [1] => something_else))
...
[100] => array ([0] => something100, [1] => something_else100))
)
would take at the most 200 iterations to find what you are looking for (if the needle were at [100][1]), with a suitable algorithm.
Linear algorithms in this case perform at O(n) (order total number of elements in entire array), this is poor, a million entries (eg a 1000x100x10 array) would take on average 500,000 iterations to find the needle. Also what would happen if you decided to change the structure of your multidimensional array? And PHP would kick out a recursive algorithm if your depth was more than 100. Computer science can do better:
Where possible, always use objects instead of multiple dimensional arrays:
ArrayObject(
MyObject(something, something_else))
...
MyObject(something100, something_else100))
)
and apply a custom comparator interface and function to sort and find them:
interface Comparable {
public function compareTo(Comparable $o);
}
class MyObject implements Comparable {
public function compareTo(Comparable $o){
...
}
}
function myComp(Comparable $a, Comparable $b){
return $a->compareTo($b);
}
You can use uasort() to utilize a custom comparator, if you're feeling adventurous you should implement your own collections for your objects that can sort and manage them (I always extend ArrayObject to include a search function at the very least).
$arrayObj->uasort("myComp");
Once they are sorted (uasort is O(n log n), which is as good as it gets over arbitrary data), binary search can do the operation in O(log n) time, ie a million entries only takes ~20 iterations to search. As far as I am aware custom comparator binary search is not implemented in PHP (array_search() uses natural ordering which works on object references not their properties), you would have to implement this your self like I do.
This approach is more efficient (there is no longer a depth) and more importantly universal (assuming you enforce comparability using interfaces) since objects define how they are sorted, so you can recycle the code infinitely. Much better =)
$result = array_filter($arr, function ($var) {
$found = false;
array_walk_recursive($var, function ($item, $key) use (&$found) {
$found = $found || $key == "name" && $item == "cat 1";
});
return $found;
});
http://snipplr.com/view/51108/nested-array-search-by-value-or-key/
<?php
//PHP 5.3
function searchNestedArray(array $array, $search, $mode = 'value') {
foreach (new RecursiveIteratorIterator(new RecursiveArrayIterator($array)) as $key => $value) {
if ($search === ${${"mode"}})
return true;
}
return false;
}
$data = array(
array('abc', 'ddd'),
'ccc',
'bbb',
array('aaa', array('yyy', 'mp' => 555))
);
var_dump(searchNestedArray($data, 555));
function in_multi_array($needle, $key, $haystack)
{
$in_multi_array = false;
if (in_array($needle, $haystack))
{
$in_multi_array = true;
}else
{
foreach( $haystack as $key1 => $val )
{
if(is_array($val))
{
if($this->in_multi_array($needle, $key, $val))
{
$in_multi_array = true;
break;
}
}
}
}
return $in_multi_array;
}
I needed something similar, but to search for multidimensional array by value... I took John example and wrote
function _search_array_by_value($array, $value) {
$results = array();
if (is_array($array)) {
$found = array_search($value,$array);
if ($found) {
$results[] = $found;
}
foreach ($array as $subarray)
$results = array_merge($results, $this->_search_array_by_value($subarray, $value));
}
return $results;
}
I hope it helps somebody :)
This is a revised function from the one that John K. posted... I need to grab only the specific key in the array and nothing above it.
function search_array ( $array, $key, $value )
{
$results = array();
if ( is_array($array) )
{
if ( $array[$key] == $value )
{
$results[] = $array;
} else {
foreach ($array as $subarray)
$results = array_merge( $results, $this->search_array($subarray, $key, $value) );
}
}
return $results;
}
$arr = array(0 => array(id=>1,name=>"cat 1"),
1 => array(id=>2,name=>"cat 2"),
2 => array(id=>3,name=>"cat 1"));
print_r(search_array($arr, 'name', 'cat 1'));
function findKey($tab, $key){
foreach($tab as $k => $value){
if($k==$key) return $value;
if(is_array($value)){
$find = findKey($value, $key);
if($find) return $find;
}
}
return null;
}
I think the easiest way is using php array functions if you know your key.
function search_array ( $array, $key, $value )
{
return array_search($value,array_column($array,$key));
}
this return an index that you could find your desired data by this like below:
$arr = array(0 => array('id' => 1, 'name' => "cat 1"),
1 => array('id' => 2, 'name' => "cat 2"),
2 => array('id' => 3, 'name' => "cat 1")
);
echo json_encode($arr[search_array($arr,'name','cat 2')]);
this output will:
{"id":2,"name":"cat 2"}
And another version that returns the key value from the array element in which the value is found (no recursion, optimized for speed):
// if the array is
$arr['apples'] = array('id' => 1);
$arr['oranges'] = array('id' => 2);
//then
print_r(search_array($arr, 'id', 2);
// returns Array ( [oranges] => Array ( [id] => 2 ) )
// instead of Array ( [0] => Array ( [id] => 2 ) )
// search array for specific key = value
function search_array($array, $key, $value) {
$return = array();
foreach ($array as $k=>$subarray){
if (isset($subarray[$key]) && $subarray[$key] == $value) {
$return[$k] = $subarray;
return $return;
}
}
}
Thanks to all who posted here.
If you want to search for array of keys this is good
function searchKeysInMultiDimensionalArray($array, $keys)
{
$results = array();
if (is_array($array)) {
$resultArray = array_intersect_key($array, array_flip($keys));
if (!empty($resultArray)) {
$results[] = $resultArray;
}
foreach ($array as $subarray) {
$results = array_merge($results, searchKeysInMultiDimensionalArray($subarray, $keys));
}
}
return $results;
}
Keys will not overwrite because each set of key => values will be in separate array in resulting array.
If you don't want duplicate keys then use this one
function searchKeysInMultiDimensionalArray($array, $keys)
{
$results = array();
if (is_array($array)) {
$resultArray = array_intersect_key($array, array_flip($keys));
if (!empty($resultArray)) {
foreach($resultArray as $key => $single) {
$results[$key] = $single;
}
}
foreach ($array as $subarray) {
$results = array_merge($results, searchKeysInMultiDimensionalArray($subarray, $keys));
}
}
return $results;
}
2 functions: array_search_key_value which returns the array of keys to reach a key with a value in a multidimensional array, array_extract_keys which returns the value in a multidimensional array pointed to by an array of keys.
function array_search_key_value($array, $key, $value) {
if (!is_array($array)) {
return false;
}
return array_search_key_value_aux($array, $key, $value);
}
function array_search_key_value_aux($array, $key, $value, $path=null) {
if (array_key_exists($key, $array) && $array[$key] === $value) {
$path[]=$key;
return $path;
}
foreach ($array as $k => $v ) {
if (is_array($v)) {
$path[]=$k;
$p = array_search_key_value_aux($v, $key, $value, $path);
if ($p !== false) {
return $p;
}
}
}
return false;
}
function array_extract_keys($array, $key_list) {
$v = $array;
foreach ($key_list as $key) {
if (!is_array($v) || !array_key_exists($key, $v))
return false;
$v = &$v[$key];
}
return $v;
}
Here is a unitary test:
$test_array = array(
'a' => array(
'aa' => true,
'ab' => array(
'aaa' => array(
'one' => 1,
'two' => 2,
'three' => 3,
'four' => 4
),
'four' => 4,
'five' => 5,
),
'six' => 6,
),
'seven' => 7
);
$test_data = array(
array('one', 1),
array('two', 2),
array('three', 3),
array('four', 4),
array('five', 5),
array('six', 6),
array('seven', 7),
array('zero', 0),
array('one', 0),
);
foreach ($test_data as $d) {
$r = array_search_key_value($test_array, $d[0], $d[1]);
echo $d[0] . ' => ' . $d[1] . ' ? ', $r ? implode('/', $r) . ' => ' . array_extract_keys($test_array, $r) : 'null', PHP_EOL;
}