Reputation: 49
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 :)
Upvotes: 1
Views: 2548
Reputation: 19512
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.
Upvotes: 2
Reputation: 57131
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);
}
Upvotes: 1