• PHP Help PHP Coding
  • [RESOLVED] Removing multiple levels of parent arrays from soap response -> array

I am working with SOAP and converting the response to an array, however, theres multiple levels of nesting/parent arrays that make further working with the data a pain in the butt namely because the array names change, etc

So, I'm taking my soap response and running it through the following to get it as an array:

$response = preg_replace("/(<\/?)(\w+):([^>]*>)/", "$1$2$3", $response);
$xml = new SimpleXMLElement($response);
return json_decode(json_encode($xml), TRUE);

When I print_r that output I get the following horror:

Array
(
    [soapBody] => Array
        (
            [ns1getMultiplePeakSensorDataResponse] => Array
                (
                    [ns1ArrayOfISXCSensorDataSet] => Array
                        (
                            [ns2ISXCSensorDataSet] => Array
                                (
                                    [0] => Array
                                        (
                                            [ns2ISXCSensorData] => Array
                                                (
                                                    [ns2timeStamp] => 2017-02-21T23:23:59Z
                                                    [ns2units] => W
                                                    [ns2value] => 490
                                                    [ns2ISXCValueType] => INTEGER
                                                )

                                        [ns2ISXCElementID] => B16b483_nbSNMPEncBAF04BFE_OUTPUT_POWER_TOTAL_1
                                    )

                                [1] => Array
                                    (
                                        [ns2ISXCSensorData] => Array
                                            (
                                                [ns2timeStamp] => 2017-02-21T23:23:59Z
                                                [ns2units] => W
                                                [ns2value] => 510.0
                                                [ns2ISXCValueType] => UNKNOWN
                                            )

                                        [ns2ISXCElementID] => B16b483_nbSNMPEncBAF04BFE_OUTPUT_POWER_TOTAL_2
                                    )

                            )

                    )

            )

    )

)

...and what I've LOVE to end up with is:

Array
(
    [0] => Array
        (
            [ns2ISXCSensorData] => Array
                (
                    [ns2timeStamp] => 2017-02-21T23:23:59Z
                    [ns2units] => W
                    [ns2value] => 490
                    [ns2ISXCValueType] => INTEGER
                )

        [ns2ISXCElementID] => B16b483_nbSNMPEncBAF04BFE_OUTPUT_POWER_TOTAL_1
    )

[1] => Array
    (
        [ns2ISXCSensorData] => Array
            (
                [ns2timeStamp] => 2017-02-21T23:23:59Z
                [ns2units] => W
                [ns2value] => 510.0
                [ns2ISXCValueType] => UNKNOWN
            )

        [ns2ISXCElementID] => B16b483_nbSNMPEncBAF04BFE_OUTPUT_POWER_TOTAL_2
    )
)

I can brute force this with:

$i=0;
while($i != 4) {
	$response = call_user_func_array('array_merge', $response); 
	$i++;
}

...but there are incidents where the nesting level isn't always four layers so it's not really a solution.

I've given a whack at quite a few solutions from the interwebs any nothing really preserves the final inner array, it either overwrites the keys down to a single result or only returns a series of values in a non-associative array.

Any thoughts or input would be greatly appreciated.

Thank you =)

    Will these names always be the same, or can they vary?

                [ns1getMultiplePeakSensorDataResponse] => Array
                    (
                        [ns1ArrayOfISXCSensorDataSet] => Array
                            (
                                [ns2ISXCSensorDataSet] => Array
    

      So if it's an array with a numeric index it's one you want to keep unchanged, but you want to peel off any associatively-indexed wrappers they may appear in?

      A quick way to distinguish would be to parse the JSON objects as PHP objects instead of associative arrays. Then, if you find you're looking at an object, iterate over its properties, and if it's an array, merge it with the results array; when iterating, each property is handled the same way, recursively.

      // Assuming $json has already come out of json_decode
      
      function collect_sensors_from_JSON($json)
      {
      	if(is_array($json))
      	{
      		yield from $json;
      	}
      	else
      	{
      		foreach($json as $property)
      		{
      			yield from collect_sensors_from_JSON($property);
      		}
      	}
      }
      
      $collected_sensors = iterator_to_array(collect_sensors_from_JSON($json), false);
      
      print_r($collected_sensors);
        NogDog;11060345 wrote:

        Will these names always be the same, or can they vary?

                    [ns1getMultiplePeakSensorDataResponse] => Array
                        (
                            [ns1ArrayOfISXCSensorDataSet] => Array
                                (
                                    [ns2ISXCSensorDataSet] => Array
        

        They will vary in name and depth depending on the soap method called.

        Weedpacket;11060347 wrote:
        // Assuming $json has already come out of json_decode
        
        function collect_sensors_from_JSON($json)
        {
        	if(is_array($json))
        	{
        		yield from $json;
        	}
        	else
        	{
        		foreach($json as $property)
        		{
        			yield from collect_sensors_from_JSON($property);
        		}
        	}
        }
        
        $collected_sensors = iterator_to_array(collect_sensors_from_JSON($json), false);
        
        print_r($collected_sensors);

        ...this throws a:

        PHP Parse error:  syntax error, unexpected '$json' (T_VARIABLE) in /.../class.php on line 214
        

        ...where line 214 is:

                yield from $json;
        

        ...in

        function reduceInheritance($json) {
            if(is_array($json)) {
                yield from $json;
            } else {
                foreach($json as $property) {
                    yield from reduceInheritance($property);
                }
            }
        }
        
        $output = [];
        foreach(reduceInheritance($json) as $tmp) {
            $output[] = $tmp;
        }
        
        print_r($output);
        

        Thanks all, its good to see familiar faces after all these years =)

        FWIW: PHP 5.6.30 (cli) (built: Jan 20 2017 14:52:10)

          So regarding the previous,

          yield from $json;

          appears to be a PHP 7.x generator delegation. I'm limited to 5.6 at the moment.

            This seems to work, as a way to get the section of interest once you already have that array:

            <?php
            
            function getArray(Array $data)
            {
                foreach($data as $key => $value)
                {
                    return is_int($key) ? $data : getArray($value);
                }
            }
            
            // TEST:
            $data = array(
                'soapBody' => array(
                    'ns1getMultiplePeakSensorDataResponse' => array(
                        'ns1arrayOfISXCSensorDataSet' => array(
                            'ns2ISXCSensorDataSet' => array(
                                0 => array(
                                    'ns2ISXCSensorData' => array(
                                        'ns2timeStamp' => '2017-02-21T23:23:59Z',
                                        'ns2units' => 'W',
                                        'ns2value' => 490,
                                        'ns2ISXCValueType' => 'INTEGER'
                                    ),
                                    'ns2ISXCElementID' => 'B16b483_nbSNMPEncBAF04BFE_OUTPUT_POWER_TOTAL_1'
                                ),
                                1 => array(
                                    'ns2ISXCSensorData' => array(
                                        'ns2timeStamp' => '2017-02-21T23:23:59Z',
                                        'ns2units' => 'W',
                                        'ns2value' => 510.0,
                                        'ns2ISXCValueType' => 'UNKNOWN'
                                    ),
                                    'ns2ISXCElementID' => 'B16b483_nbSNMPEncBAF04BFE_OUTPUT_POWER_TOTAL_2'
                                )
                            )
                        )
                    )
                )
            );
            
            $result = getArray($data);
            print_r($result);
            echo PHP_EOL;
            
              NogDog;11060361 wrote:

              This seems to work, as a way to get the section of interest once you already have that array:
              ...

              We must be on the same wavelength because I just finished something similar:

              function flatten( $arr ) {
              	if(is_array($arr)) {
              		foreach($arr as $k => $v) {
              			if(!is_int($k)) {
              				flatten($v);
              			} else {
              				return $arr; //Results in NULL
              				print_r($arr); //Shows array
              			}
              		}
              	}
              }
              

              ...albeit I was fighting with mine not returning the array (it comes out NULL on the outside) but print_r inside working fine.

              Yours works without issue.

              Thanks all =)

                Something just occurred to me: I think I remember dealing with XML in some vaguely similar way, where if there is only one "thing" in an element, then it does not treat it as an array. So if that's a possibility with your XML data, you might end up with something like the following, which would probably either return nothing or throw an error when it sends something to the recursive function that is not an array. I'm not 100% sure about this -- you may want to see if you can set up a test case where there would be just one sensorDataSet and see what you get.

                $data = array(
                    'soapBody' => array(
                        'ns1getMultiplePeakSensorDataResponse' => array(
                            'ns1arrayOfISXCSensorDataSet' => array(
                                'ns2ISXCSensorDataSet' => array(
                                    'ns2ISXCSensorData' => array(
                                        'ns2timeStamp' => '2017-02-21T23:23:59Z',
                                        'ns2units' => 'W',
                                        'ns2value' => 490,
                                        'ns2ISXCValueType' => 'INTEGER'
                                    ),
                                    'ns2ISXCElementID' => 'B16b483_nbSNMPEncBAF04BFE_OUTPUT_POWER_TOTAL_1'
                                )
                            )
                        )
                    )
                );
                

                  Okay ... in 5.6:

                  function CollectSensorsFromJSON($json)
                  {
                  	if(is_array($json))
                  	{
                  		return $json;
                  	}
                  	else
                  	{
                  		return array_merge(...array_map('CollectSensorsFromJSON', array_values((array)$json)));
                  	}
                  }
                  
                  $collected_sensors = CollectSensorsFromJSON($json);
                  
                    NogDog;11060367 wrote:

                    Something just occurred to me: I think I remember dealing with XML in some vaguely similar way, where if there is only one "thing" in an element, then it does not treat it as an array. So if that's a possibility with your XML data, you might end up with something like the following, which would probably either return nothing or throw an error when it sends something to the recursive function that is not an array. I'm not 100% sure about this -- you may want to see if you can set up a test case where there would be just one sensorDataSet and see what you get.

                    Yup, I just came across one:

                    Array
                    (
                        [Body] => Array
                            (
                                [getMultiplePeakSensorDataResponse] => Array
                                    (
                                        [ArrayOfISXCSensorDataSet] => Array
                                            (
                                                [ISXCSensorDataSet] => Array
                                                    (
                                                        [ISXCSensorData] => Array
                                                            (
                                                                [timeStamp] => 2017-02-21T23:23:59Z
                                                                [units] => W
                                                                [value] => 490
                                                                [ISXCValueType] => INTEGER
                                                            )
                    
                                                    [ISXCElementID] => B16b483_nbSNMPEncBAF04BFE_OUTPUT_POWER_TOTAL_1
                                                )
                    
                                        )
                    
                                )
                    
                        )
                    
                    )
                    

                    Having a think on how'd I'd adapt it... In this case, we'd want to shed all inheritance until an array is found to contain more than just a single array.

                    In this example we'd want:

                    Array
                    (
                        [0] => Array
                    		(
                    		[timeStamp] => 2017-02-21T23:23:59Z
                    		[units] => W
                    		[value] => 490
                    		[ISXCValueType] => INTEGER
                    		)
                    )
                    

                      So this works to an extent:

                      	public function flattenArray2(Array $arr) {
                      		if(is_array($arr)) {
                      			foreach($arr as $k => $v) {
                      				if(count($v) == 1 && is_array($v)) {
                      					$this->flattenArray2($v);
                      				} else {
                      					return $arr;
                      				}
                      			}
                      		} else {
                      			return $arr;
                      		}
                      	}
                      

                      The problem I'm still having is, the return wont actually return the array, but, if I print_r instead, it's there.

                      However, it renders (in the case of one item)

                      Array
                      (
                          [ISXCSensorDataSet] => Array
                              (
                                  [ISXCSensorData] => Array
                                      (
                                          [timeStamp] => 2017-02-21T23:23:59Z
                                          [units] => W
                                          [value] => 490
                                          [ISXCValueType] => INTEGER
                                      )
                      
                              [ISXCElementID] => B16b483_nbSNMPEncBAF04BFE_OUTPUT_POWER_TOTAL_1
                          )
                      
                      )
                      

                      ...and in the case of multiple items:

                      Array
                      (
                          [ISXCSensorDataSet] => Array
                              (
                                  [0] => Array
                                      (
                                          [ISXCSensorData] => Array
                                              (
                                                  [timeStamp] => 2017-02-21T23:23:59Z
                                                  [units] => W
                                                  [value] => 490
                                                  [ISXCValueType] => INTEGER
                                              )
                      
                                      [ISXCElementID] => B16b483_nbSNMPEncBAF04BFE_OUTPUT_POWER_TOTAL_1
                                  )
                      
                              [1] => Array
                                  (
                                      [ISXCSensorData] => Array
                                          (
                                              [timeStamp] => 2017-02-21T23:23:59Z
                                              [units] => W
                                              [value] => 510.0
                                              [ISXCValueType] => UNKNOWN
                                          )
                      
                                      [ISXCElementID] => B16b483_nbSNMPEncBAF04BFE_OUTPUT_POWER_TOTAL_2
                                  )
                      
                          )
                      
                      )
                      

                      I can likely refine that further, but, why the heck won't that function actually return my array?!

                        Reading through I notice that one reason you've sometimes got "ns2ISXCSensorData" and sometimes it's just "ISXCSensorData" is that you're mushing the namespace prefix up against the element name. If you didn't do that then (depending on what the XML and its namespaces looked like before it sent through SimpleXML and then through JSON's digestive tract) an XPath query of [font=monospace]//ISXCSensorDataSet[/font] on the XML might be enough to pull all those [font=monospace]iSXCSensorData[/font]/[font=monospace]iSXCElementID[/font] pairs wherever they are in the overall XML document.

                          Weedpacket;11060381 wrote:

                          Reading through I notice that one reason you've sometimes got "ns2ISXCSensorData" and sometimes it's just "ISXCSensorData" is that you're mushing the namespace prefix up against the element name.

                          Yes I've modified my original preg_replace since dropping $2 as I don't need the namespacing - from:

                          $response = preg_replace("/(<\/?)(\w+):([^>]*>)/", "$1$2$3", $response); 
                          

                          ...to:

                          $response = preg_replace("/(<\/?)(\w+):([^>]*>)/", "$1$3", $response);
                          
                          Weedpacket;11060381 wrote:

                          If you didn't do that then (depending on what the XML and its namespaces looked like before it sent through SimpleXML and then through JSON's digestive tract) an XPath query of [font=monospace]//ISXCSensorDataSet[/font] on the XML might be enough to pull all those [font=monospace]iSXCSensorData[/font]/[font=monospace]iSXCElementID[/font] pairs wherever they are in the overall XML document.

                          The issue is that the methods all return a different hierarchy, some return datasets, some return sensorfields, some return sensors, etc.

                          I won't be able to take into account every nuance to make it 100% dynamic - I just need to trim the fluff out and make what I hand back as XML,JSON,ARRAY be predictable and as lightweight as possible.

                          Will pop an update a little later; guess I shouldn't have marked this resolved so soon 😉

                            So I've got this working nicely:

                            	public function flattenArray2(Array $arr) {
                            		if(is_array($arr)) {
                            			foreach($arr as $k => $v) {
                            				if(count($v) == 1 && is_array($v)) {
                            					$this->flattenArray2($v);
                            				} else {
                            					if(isset($v[0])) {
                            						return $v;
                            					} else {
                            						$tmp = [];
                            						$tmp[] = $v;
                            						return $tmp ;
                            					}
                            				}
                            			}
                            		} else {
                            			return 'trouble dr jones';
                            		}
                            	}
                            
                            Array
                            (
                                [0] => Array
                                    (
                                        [ISXCSensorData] => Array
                                            (
                                                [timeStamp] => 2017-02-21T23:23:59Z
                                                [units] => W
                                                [value] => 490
                                                [ISXCValueType] => INTEGER
                                            )
                            
                                    [ISXCElementID] => B16b483_nbSNMPEncBAF04BFE_OUTPUT_POWER_TOTAL_1
                                )
                            
                            )
                            
                            Array
                            (
                                [0] => Array
                                    (
                                        [ISXCSensorData] => Array
                                            (
                                                [timeStamp] => 2017-02-21T23:23:59Z
                                                [units] => W
                                                [value] => 490
                                                [ISXCValueType] => INTEGER
                                            )
                            
                                    [ISXCElementID] => B16b483_nbSNMPEncBAF04BFE_OUTPUT_POWER_TOTAL_1
                                )
                            
                            [1] => Array
                                (
                                    [ISXCSensorData] => Array
                                        (
                                            [timeStamp] => 2017-02-21T23:23:59Z
                                            [units] => W
                                            [value] => 510.0
                                            [ISXCValueType] => UNKNOWN
                                        )
                            
                                    [ISXCElementID] => B16b483_nbSNMPEncBAF04BFE_OUTPUT_POWER_TOTAL_2
                                )
                            
                            )
                            

                            My ONLY issue is that the function will NOT return anything... I can print_r till the cows come home and see my results, but as soon as I attempt to return those arrays, I get a null variable outside.

                            It's been a bit since I've worked with classes and the rest of my class works fine but for whatever reason, this function refuses to return anything.

                            If I print_r the return of the function outside the class I get nothing and if I var_dump it, I get NULL.

                              Annnnd I sorted the issue by changing:

                              if(count($v) == 1 && is_array($v)) { 
                                        $this->flattenArray2($v); 
                               }
                              

                              ...to:

                              if(count($v) == 1 && is_array($v)) { 
                                        return $this->flattenArray2($v); 
                               }
                              

                              Missing return in the recursion, d0h!

                                Write a Reply...