0

I am working with soap responses that contain nested wrappers and whose child(ren) have nested properties.

I am trying to flatten these response to:

  1. remove the wrappers
  2. flatten the children
  3. maintain the individual children (dimensions)

I am currently working with the following that achieves #1 and #3 however it does not flatten the inner children. Note that $this->response is converted from a stdClass to Array before being flattened.

How can I also flatten down the inner nested child elements?

private function toArray()
{
    $this->response = json_decode(json_encode($this->response), true);
    return $this;
}

private function flatten($array = null)
{
    if (is_null($array)) {
        $array = $this->response;
    }
    if (is_array($array)) {
        foreach ($array as $k => $v) {
            if (count($v) == 1 && is_array($v)) {
                return $this->flatten($v);
            }
            if (isset($v[0])) {
                $this->response = $v;
                return $this;
            }
            unset($this->response);
            $this->response[] = $v;
            return $this;
        }
    }
}

...which will transform this:

stdClass Object
(
[ArrayOfDevice] => stdClass Object
    (
        [Device] => Array
            (
                [0] => stdClass Object
                    (
                        [NamedElement] => stdClass Object
                            (
                                [Element] => stdClass Object
                                    (
                                        [ElementType] => DEVICE
                                        [id] => Device1ID
                                    )

                                [name] => Device1
                            )
                        [hostName] => Device1.hostname
                        [ipAddress] => Device1.ip
                        [location] => location1
                        [modelName] => 
                        [modelNumber] =>
                        [parentID] => xxxYYY
                        [serialNumber] => 123456789
                    )

                [1] => stdClass Object
                    (
                        [NamedElement] => stdClass Object
                            (
                                [Element] => stdClass Object
                                    (
                                        [ElementType] => DEVICE
                                        [id] => Device2ID
                                    )

                                [name] => Device2
                            )

                        [hostName] => Device2.hostname
                        [ipAddress] => Device2.ip
                        [location] => location1
                        [modelName] =>
                        [modelNumber] =>
                        [parentID] => xxxYYY
                        [serialNumber] => 987654321
                    )
            )
    )
)

...to this:

Array
(
[0] => Array
    (
        [NamedElement] => Array
            (
                [Element] => Array
                    (
                        [ElementType] => DEVICE
                        [id] => Device1ID
                    )

                [name] => Device1
            )
            [hostName] => Device1.hostname
            [ipAddress] => Device1.ip
            [location] => location1
            [modelName] => 
            [modelNumber] =>
            [parentID] => xxxYYY
            [serialNumber] => 123456789
    )

[1] => Array
    (
        [NamedElement] => Array
            (
                [Element] => Array
                    (
                        [ElementType] => DEVICE
                        [id] => Device2ID
                    )

                [name] => Device2
            )
            [hostName] => Device2.hostname
            [ipAddress] => Device2.ip
            [location] => location1
            [modelName] =>
            [modelNumber] =>
            [parentID] => xxxYYY
            [serialNumber] => 987654321
    )
)

...but I'd prefer:

Array
(
[0] => Array
    (
            [ElementType] => DEVICE
            [id] => Device1ID
            [name] => Device1
            [hostName] => Device1.hostname
            [ipAddress] => Device1.ip
            [location] => location1
            [modelName] => 
            [modelNumber] =>
            [parentID] => xxxYYY
            [serialNumber] => 123456789
    )

[1] => Array
    (
            [ElementType] => DEVICE
            [id] => Device2ID
            [name] => Device2
            [hostName] => Device2.hostname
            [ipAddress] => Device2.ip
            [location] => location1
            [modelName] =>
            [modelNumber] =>
            [parentID] => xxxYYY
            [serialNumber] => 987654321
    )
)

...and in the case of a single item being returned, this:

stdClass Object
(
[ArrayOfAlarm] => stdClass Object
    (
        [Alarm] => stdClass Object
            (
                [Element] => stdClass Object
                    (
                        [ElementType] => ALARM
                        [id] => Alarm1ID
                    )

                [activeTime] => 
                [AlarmSeverity] => 
                [AlarmState] => 
                [description] => 
                [deviceID] => 
                [recommendedAction] =>
                [resolvedTime] =>
                [sensorID] => 
            )

    )

)

...to this:

Array
(
[0] => Array
    (
        [Element] => Array
            (
                [ElementType] => ALARM
                [id] => Alarm1ID
            )

        [activeTime] =>
        [AlarmSeverity] =>
        [AlarmState] =>
        [description] =>
        [deviceID] =>
        [recommendedAction] =>
        [resolvedTime] =>
        [sensorID] =>
    )
)

...but I'd prefer:

Array
(
[0] => Array
    (
        [ElementType] => ALARM
        [id] => Alarm1ID
        [activeTime] =>
        [AlarmSeverity] =>
        [AlarmState] =>
        [description] =>
        [deviceID] =>
        [recommendedAction] =>
        [resolvedTime] =>
        [sensorID] =>
    )
)
  • Getting to the object down to a flattened array allows me to filter/sort that I cannot do with the stdClass Object – ironchefbadass Aug 06 '19 at 19:31
  • This task is solved by combining [Convert a PHP object to an associative array](https://stackoverflow.com/a/16111687/2943403) then make looped call of a technique from [How to Flatten a Multidimensional Array?](https://stackoverflow.com/q/1319903/2943403) which can handle more than two levels of depth (as shown by Arleigh Hix). – mickmackusa Sep 28 '22 at 19:55

2 Answers2

1

You can flatten a single of your items with the following function:

function flatten_item($array)
{
    $result = [];
    foreach ($array as $k => $v) {
        if (is_array($v)) {
            $result = array_merge($result, $this->flatten_item($v));
        } else {
            $result[$k] = $v;
        }
    }
    return $result;
}

When you have an array of results, you can pass this function as the callback to array_map. Only the relevant portion of the code:

if (isset($v[0])) {
    $this->response = array_map([$this, 'flatten_item'], $v);
    return $this;
}
// Convert single result to an array
$this->response = [$this->flatten_item($v)];
return $this;

Since the response (so far) always has the same structure, you could extract the payload without using recursion, which allows you to also remove the foreach in the flatten function:

function flatten()
{
    // Remove outer wrappers [ArrayOfX][X] by extracting the value
    $payload = current(current($this->response));
    if (isset($payload[0])) {
        $this->response = array_map([$this, 'flatten_item'], $payload);
    } else {
        $this->response = [$this->flatten_item($payload)];
    }
    return $this;
}
msg
  • 7,863
  • 3
  • 14
  • 33
0

The function found here does just what you want, with a slight modification (commented out concatination of parent keys): https://gist.github.com/kohnmd/11197713#gistcomment-1895523

function flattenWithKeys(array $array, $childPrefix = '.', $root = '', $result = array()) {
    foreach($array as $k => $v) {
        if(is_array($v) || is_object($v)) $result = flattenWithKeys( (array) $v, $childPrefix, $root . $k . $childPrefix, $result);
        else $result[ /*$root .*/ $k ] = $v;
    }
    return $result;
}

Letting $object equal your provided object, use as follows:

$array = json_decode(json_encode($object), true);

$result =[];             
foreach( $array['ArrayOfDevice']['Device'] as $key => $value ){
    $result[$key] = flattenWithKeys($value);
}
print_r($result);

Output:

Array
(
    [0] => Array
        (
            [ElementType] => DEVICE
            [id] => Device1ID
            [hostName] => Device1.hostname
            [ipAddress] => Device1.ip
            [location] => location1
            [modelName] => 
            [modelNumber] => 
            [parentID] => xxxYYY
            [serialNumber] => 123456789
        )

    [1] => Array
        (
            [ElementType] => DEVICE
            [id] => Device2ID
            [name] => Device2
            [hostName] => Device2.hostname
            [ipAddress] => Device2.ip
            [location] => location2
            [modelName] => 
            [modelNumber] => 
            [parentID] => xxxYYY
            [serialNumber] => 987654321
        )

)

See it run here: http://sandbox.onlinephpfunctions.com/code/851e93389b993a0e44c1e916291dc444f47047d3

Arleigh Hix
  • 9,990
  • 1
  • 14
  • 31