I'm using the Oxford Dictionary API to look up word definitions. The JSON returned could be 13 levels deep or more. It's arrays of objects, and I can never be certain an object will exist at any point.
I want to return a simple array of the definitions, stored in $list.
So I found myself writing this horror story:
$list = [];
$data = json_decode( $response->getBody() );
//oh gross
if( isset( $data->results ) ) {
foreach( $data->results as $r ) {
if( isset( $r->lexicalEntries ) ) {
foreach( $r->lexicalEntries as $l ) {
if( isset( $l->entries ) ) {
foreach( $l->entries as $e ) {
if( isset( $e->senses ) ) {
foreach( $e->senses as $s ) {
if( isset( $s->definitions ) ) {
foreach( $s->definitions as $d ) {
$list[] = [
'word' => $r->word,
'definition' => $d
];
}
}
if( isset( $s->subsenses ) ) {
foreach( $s->subsenses as $ss ) {
if( isset( $ss->definitions ) ) {
foreach( $ss->definitions as $d ) {
$list[] = [
'word' => $r->word,
'definition' => $d
];
}
}
}
}
}
}
}
}
}
}
}
}
return $list;
Which works, but is just completely nasty.
I've considered a recursive function but not sure that would be any more readable.
Is there a more elegant way?
Well, I gotta be honest as that result of your method is just an array with two indexes, where word is always the same and the definition is different. If you really want $r->word stored with that definition than there is little that can be done.
However, you can shorten your isset() code with:
And example for both versions:
// PHP < 7.
foreach(isset($data['results']) ? $data['results'] : [] as $value){}
// PHP > 7
foreach($data['results'] ?? [] as $value){}
However, this is all just to suppress the error reporting, you can temporarily turn it off and back on again and just assume the index exist and forget about the isset().
I've defined the data like so:
$json = file_get_contents('data.json');
$data = json_decode($json, true);
As I prefer arrays over objects in this occasion, and a copy of your method of doing things with the PHP 7 null coalesce operator:
foreach($data['results'] ?? [] as $r){
foreach($r['lexicalEntries'] ?? [] as $l){
foreach($l['entries'] ?? [] as $e){
foreach($e['senses'] ?? [] as $s){
foreach( $s['definitions'] ?? [] as $d){
$list[] = [
'word' => $r['word'],
'definition' => $d
];
}
foreach($s['subsenses'] ?? [] as $ss){
foreach( $ss['definitions'] ?? [] as $d){
$list[] = [
'word' => $r['word'],
'definition' => $d
];
}
}
}
}
}
}
print_r($list);
However, there are other ways you can do this and that is to flatten the array. There are multiple ways you can do this, I prefer a generator however recursive generators are introduced in PHP 7 and above.
// This is a generator function, it flattens the entire given array.
// It yields an array containing the key and value on each iteration.
function array_walk_generator($array){
foreach($array as $k => $v){
yield $k => $v;
if(is_array($v)){
yield from array_walk_generator($v);
}
}
}
foreach($data['results'] as $result){
$list = [];
foreach(array_walk_generator($result) as $k => $v){
if($k == 'definitions' && is_array($v)){
$list[] = $v[0];
}
}
$return[] = [
'word' => $result['word'],
'definitions' => array_values(array_filter($list))
];
}
print_r($return);
This way you have in my opinion a better organized array, and this way it doesn't really care how the definitions are formatted.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With