1

We are using SimpleXML to try and convert XML to JSON, and in turn convert to a PHP object, so that we can compare out Soap API with our Rest API. We have a request that returns quite a lot of data, but the part in question is where we have a nested array.

The array is returned with the tag in XML, however we do not want this translated into the JSON.

The XML that we get is as follows:

                  <apns>
                     <item>
                        <apn>apn</apn>
                     </item>
                  </apns>

So when it is translated into JSON it looks like this:

{"apns":{"item":{"apn":"apn"}}

In reality, we want SimpleXML to convert to the same JSON as in our Rest API, which looks like the following:

{"apns":[{"apn":"apn"}]}

The array could contain more than one thing, for example:

                  <apns>
                     <item>
                        <apn>apn</apn>
                     </item>
                     <item>
                        <apn>apn2</apn>
                     </item>
                  </apns>

Which I'm assuming will just error in JSON or have the first one overwritten.

I'd expect SimpleXML to be able to handle this natively, but if not has anyone got a fix that doesn't involve janky string manipulation?

TIA :)

4
  • It might be useful to see how you are actually doing the existsing conversion Commented Aug 21, 2019 at 14:25
  • "I'd expect SimpleXML to be able to handle this natively" LOL if a PHP extension designed to parse XML was randomly dropping elements, we'd call it broken. Why don't you fix one of your APIs to return the same format as the other? Commented Aug 21, 2019 at 18:26
  • "I'm assuming will just error in JSON or have the first one overwritten." Did you test this assumption? Commented Aug 21, 2019 at 18:26
  • You mention that once you've generated the JSON you convert to a PHP object. What form does that object need to take, and could you generate it directly from the XML? Since XML, JSON, and PHP have fundamentally different models of objects, arrays, etc, blindly converting from one to another is always going to be problematic. Commented Aug 22, 2019 at 14:58

2 Answers 2

2

A generic conversion has no possibility to know that a single element should be an array in JSON.

SimpleXMLElement properties can be treated as an Iterable to traverse sibling with the same name. They can be treated as an list or a single value.

This allows you to build up your own array/object structure and serialize it to JSON.

$xml = <<<'XML'
<apns>
  <item>
    <apn>apn1</apn>
  </item>
  <item>
    <apn>apn2</apn>
  </item>
</apns>
XML;

$apns = new SimpleXMLElement($xml);

$json = [
    'apns' => []
];
foreach ($apns->item as $item) {
    $json['apns'][] = ['apn' => (string)$item->apn]; 
}

echo json_encode($json, JSON_PRETTY_PRINT);

This still allows you to read/convert parts in a general way. Take a more in deep look at the SimpleXMLElement class. Here are method to iterate over all children or to get the name of the current node.

Sign up to request clarification or add additional context in comments.

Comments

1

I hope this code is useful as a template to what your after, the problem is that it's difficult to know if this is the only instance of what your trying to do...

What this does is first looks for any nodes which have a item/apn structure underneath using XPath (//*[item/apn] says any node //* with the following nodes underneath).

Then it loops through these items and adds new <apn> nodes underneath the start node (the <apns> node in this case) from each <item> with the value ($list->addChild("apn", (string)$item->apn);.

Once the nodes are copied it removes all of the <item> nodes (unset($list->item);).

$input = '<apns>
                     <item>
                        <apn>apn</apn>
                     </item>
                     <item>
                        <apn>apn2</apn>
                     </item>
                  </apns>';

$xml = simplexml_load_string($input);
$itemList = $xml->xpath("//*[item/apn]");

foreach ( $itemList as $list ) {
    foreach ( $list->item as $item )  {
        $list->addChild("apn", (string)$item->apn);
    }
    unset($list->item);
}

echo $xml->asXML();

gives...

<?xml version="1.0"?>
<apns>


                  <apn>apn</apn><apn>apn2</apn></apns>

and

echo json_encode($xml);

gives...

{"apn":["apn","apn2"]}

If you just want the last value, then you can just keep track of the last value and set the new element outside the inner loop...

$itemList = $xml->xpath("//*[item/apn]");

foreach ( $itemList as $list ) {
    foreach ( $list->item as $item )  {
        $apn = (string)$item->apn;
    }
    $list->addChild("apn", $apn);
    unset($list->item);
}

5 Comments

Thanks for the answer :) Been playing with this for a couple of hours now. Unfortunately I keep ending up with the following: XML: <apns><apn>apn</apn><apn>apn2</apn></apns> and when I JSON Encode it I get "apns":{"apn":["apn","apn2"]}. I was thinking of converting to JSON and then working with the item property, although this is for a production application so some random regexes and preg_replaces may not be favourable.
What your getting ({"apn":["apn","apn2"]}) is actually recognising the multiple values,which I would have thought it was better. Do you just want the last one?
Added a new code sample for just having the last value.
As it looks as though you don't use json_encode() - how do you encode the data?
So this ended up working to convert it at only that level. For some reason it still gave me issues when I converted large amount of XML. I did the final fix in PHP, as it was easy to remove the items tag

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.