PHP convert XML to JSON

2018-12-31 16:49发布

问题:

I am trying to convert xml to json in php. If I do a simple convert using simple xml and json_encode none of the attributes in the xml show.

$xml = simplexml_load_file(\"states.xml\");
echo json_encode($xml);

So I am trying to manually parse it like this.

foreach($xml->children() as $state)
{
    $states[]= array(\'state\' => $state->name); 
}       
echo json_encode($states);

and the output for state is {\"state\":{\"0\":\"Alabama\"}} rather than {\"state\":\"Alabama\"}

What am I doing wrong?

XML:

<?xml version=\"1.0\" ?>
<states>
    <state id=\"AL\">     
    <name>Alabama</name>
    </state>
    <state id=\"AK\">
        <name>Alaska</name>
    </state>
</states>

Output:

[{\"state\":{\"0\":\"Alabama\"}},{\"state\":{\"0\":\"Alaska\"}

var dump:

object(SimpleXMLElement)#1 (1) {
[\"state\"]=>
array(2) {
[0]=>
object(SimpleXMLElement)#3 (2) {
  [\"@attributes\"]=>
  array(1) {
    [\"id\"]=>
    string(2) \"AL\"
  }
  [\"name\"]=>
  string(7) \"Alabama\"
}
[1]=>
object(SimpleXMLElement)#2 (2) {
  [\"@attributes\"]=>
  array(1) {
    [\"id\"]=>
    string(2) \"AK\"
  }
  [\"name\"]=>
  string(6) \"Alaska\"
}
}
}

回答1:

Json & Array from XML in 3 lines:

$xml = simplexml_load_string($xml_string);
$json = json_encode($xml);
$array = json_decode($json,TRUE);


回答2:

Sorry for answering an old post, but this article outlines an approach that is relatively short, concise and easy to maintain. I tested it myself and works pretty well.

http://lostechies.com/seanbiefeld/2011/10/21/simple-xml-to-json-with-php/

<?php   
class XmlToJson {
    public function Parse ($url) {
        $fileContents= file_get_contents($url);
        $fileContents = str_replace(array(\"\\n\", \"\\r\", \"\\t\"), \'\', $fileContents);
        $fileContents = trim(str_replace(\'\"\', \"\'\", $fileContents));
        $simpleXml = simplexml_load_string($fileContents);
        $json = json_encode($simpleXml);

        return $json;
    }
}
?>


回答3:

I figured it out. json_encode handles objects differently than strings. I cast the object to a string and it works now.

foreach($xml->children() as $state)
{
    $states[]= array(\'state\' => (string)$state->name); 
}       
echo json_encode($states);


回答4:

I guess I\'m a bit late to the party but I have written a small function to accomplish this task. It also takes care of attributes, text content and even if multiple nodes with the same node-name are siblings.

Dislaimer: I\'m not a PHP native, so please bear with simple mistakes.

function xml2js($xmlnode) {
    $root = (func_num_args() > 1 ? false : true);
    $jsnode = array();

    if (!$root) {
        if (count($xmlnode->attributes()) > 0){
            $jsnode[\"$\"] = array();
            foreach($xmlnode->attributes() as $key => $value)
                $jsnode[\"$\"][$key] = (string)$value;
        }

        $textcontent = trim((string)$xmlnode);
        if (count($textcontent) > 0)
            $jsnode[\"_\"] = $textcontent;

        foreach ($xmlnode->children() as $childxmlnode) {
            $childname = $childxmlnode->getName();
            if (!array_key_exists($childname, $jsnode))
                $jsnode[$childname] = array();
            array_push($jsnode[$childname], xml2js($childxmlnode, true));
        }
        return $jsnode;
    } else {
        $nodename = $xmlnode->getName();
        $jsnode[$nodename] = array();
        array_push($jsnode[$nodename], xml2js($xmlnode, true));
        return json_encode($jsnode);
    }
}   

Usage example:

$xml = simplexml_load_file(\"myfile.xml\");
echo xml2js($xml);

Example Input (myfile.xml):

<family name=\"Johnson\">
    <child name=\"John\" age=\"5\">
        <toy status=\"old\">Trooper</toy>
        <toy status=\"old\">Ultrablock</toy>
        <toy status=\"new\">Bike</toy>
    </child>
</family>

Example output:

{\"family\":[{\"$\":{\"name\":\"Johnson\"},\"child\":[{\"$\":{\"name\":\"John\",\"age\":\"5\"},\"toy\":[{\"$\":{\"status\":\"old\"},\"_\":\"Trooper\"},{\"$\":{\"status\":\"old\"},\"_\":\"Ultrablock\"},{\"$\":{\"status\":\"new\"},\"_\":\"Bike\"}]}]}]}

Pretty printed:

{
    \"family\" : [{
            \"$\" : {
                \"name\" : \"Johnson\"
            },
            \"child\" : [{
                    \"$\" : {
                        \"name\" : \"John\",
                        \"age\" : \"5\"
                    },
                    \"toy\" : [{
                            \"$\" : {
                                \"status\" : \"old\"
                            },
                            \"_\" : \"Trooper\"
                        }, {
                            \"$\" : {
                                \"status\" : \"old\"
                            },
                            \"_\" : \"Ultrablock\"
                        }, {
                            \"$\" : {
                                \"status\" : \"new\"
                            },
                            \"_\" : \"Bike\"
                        }
                    ]
                }
            ]
        }
    ]
}

Quirks to keep in mind: Several tags with the same tagname can be siblings. Other solutions will most likely drop all but the last sibling. To avoid this each and every single node, even if it only has one child, is an array which hold an object for each instance of the tagname. (See multiple \"\" elements in example)

Even the root element, of which only one should exist in a valid XML document is stored as array with an object of the instance, just to have a consistent data structure.

To be able to distinguish between XML node content and XML attributes each objects attributes are stored in the \"$\" and the content in the \"_\" child.

Edit: I forgot to show the output for your example input data

{
    \"states\" : [{
            \"state\" : [{
                    \"$\" : {
                        \"id\" : \"AL\"
                    },
                    \"name\" : [{
                            \"_\" : \"Alabama\"
                        }
                    ]
                }, {
                    \"$\" : {
                        \"id\" : \"AK\"
                    },
                    \"name\" : [{
                            \"_\" : \"Alaska\"
                        }
                    ]
                }
            ]
        }
    ]
}


回答5:

A common pitfall is to forget that json_encode() does not respect elements with a textvalue and attribute(s). It will choose one of those, meaning dataloss. The function below solves that problem. If one decides to go for the json_encode/decode way, the following function is advised.

function json_prepare_xml($domNode) {
  foreach($domNode->childNodes as $node) {
    if($node->hasChildNodes()) {
      json_prepare_xml($node);
    } else {
      if($domNode->hasAttributes() && strlen($domNode->nodeValue)){
         $domNode->setAttribute(\"nodeValue\", $node->textContent);
         $node->nodeValue = \"\";
      }
    }
  }
}

$dom = new DOMDocument();
$dom->loadXML( file_get_contents($xmlfile) );
json_prepare_xml($dom);
$sxml = simplexml_load_string( $dom->saveXML() );
$json = json_decode( json_encode( $sxml ) );

by doing so, <foo bar=\"3\">Lorem</foo> will not end up as {\"foo\":\"Lorem\"} in your JSON.



回答6:

Try to use this

$xml = ... // Xml file data

// first approach
$Json = json_encode(simplexml_load_string($xml));

---------------- OR -----------------------

// second approach
$Json = json_encode(simplexml_load_string($xml, \"SimpleXMLElement\", LIBXML_NOCDATA));

echo $Json;

Or

You can use this library : https://github.com/rentpost/xml2array



回答7:

Optimizing Antonio Max answer:

$xmlfile = \'yourfile.xml\';
$xmlparser = xml_parser_create();

// open a file and read data
$fp = fopen($xmlfile, \'r\');
//9999999 is the length which fread stops to read.
$xmldata = fread($fp, 9999999);

// converting to XML
$xml = simplexml_load_string($xmldata, \"SimpleXMLElement\", LIBXML_NOCDATA);

// converting to JSON
$json = json_encode($xml);
$array = json_decode($json,TRUE);


回答8:

I\'ve used Miles Johnson\'s TypeConverter for this purpose. It\'s installable using Composer.

You could write something like this using it:

<?php
require \'vendor/autoload.php\';
use mjohnson\\utility\\TypeConverter;

$xml = file_get_contents(\"file.xml\");
$arr = TypeConverter::xmlToArray($xml, TypeConverter::XML_GROUP);
echo json_encode($arr);


回答9:

This is an improvement of the most upvoted solution by Antonio Max, which also works with XML that has namespaces (by replacing the colon with an underscore). It also has some extra options (and does parse <person my-attribute=\'name\'>John</person> correctly).

function parse_xml_into_array($xml_string, $options = array()) {
    /*
    DESCRIPTION:
    - parse an XML string into an array
    INPUT:
    - $xml_string
    - $options : associative array with any of these keys:
        - \'flatten_cdata\' : set to true to flatten CDATA elements
        - \'use_objects\' : set to true to parse into objects instead of associative arrays
        - \'convert_booleans\' : set to true to cast string values \'true\' and \'false\' into booleans
    OUTPUT:
    - associative array
    */

    // Remove namespaces by replacing \":\" with \"_\"
    if (preg_match_all(\"|</([\\\\w\\\\-]+):([\\\\w\\\\-]+)>|\", $xml_string, $matches, PREG_SET_ORDER)) {
        foreach ($matches as $match) {
            $xml_string = str_replace(\'<\'. $match[1] .\':\'. $match[2], \'<\'. $match[1] .\'_\'. $match[2], $xml_string);
            $xml_string = str_replace(\'</\'. $match[1] .\':\'. $match[2], \'</\'. $match[1] .\'_\'. $match[2], $xml_string);
        }
    }

    $output = json_decode(json_encode(@simplexml_load_string($xml_string, \'SimpleXMLElement\', ($options[\'flatten_cdata\'] ? LIBXML_NOCDATA : 0))), ($options[\'use_objects\'] ? false : true));

    // Cast string values \"true\" and \"false\" to booleans
    if ($options[\'convert_booleans\']) {
        $bool = function(&$item, $key) {
            if (in_array($item, array(\'true\', \'TRUE\', \'True\'), true)) {
                $item = true;
            } elseif (in_array($item, array(\'false\', \'FALSE\', \'False\'), true)) {
                $item = false;
            }
        };
        array_walk_recursive($output, $bool);
    }

    return $output;
}


回答10:

If you would like to only convert a specific part of the XML to JSON, you can use XPath to retrieve this and convert that to JSON.

<?php
$file = @file_get_contents($xml_File, FILE_TEXT);
$xml = new SimpleXMLElement($file);
$xml_Excerpt = @$xml->xpath(\'/states/state[@id=\"AL\"]\')[0]; // [0] gets the node
echo json_encode($xml_Excerpt);
?>

Please note that if you Xpath is incorrect, this will die with an error. So if you\'re debugging this through AJAX calls I recommend you log the response bodies as well.



回答11:

Looks like the $state->name variable is holding an array. You can use

var_dump($state)

inside the foreach to test that.

If that\'s the case, you can change the line inside the foreach to

$states[]= array(\'state\' => array_shift($state->name)); 

to correct it.



回答12:

The question doesn\'t say it, but usually PHP is returning JSON to a web page.

I find it much easier to convert the XML to JSON in the browser/page via a JS lib, for example:

https://code.google.com/p/x2js/downloads/detail?name=x2js-v1.1.3.zip


回答13:

All solutions here have problems!

... When the representation need perfect XML interpretation (without problems with attributes) and to reproduce all text-tag-text-tag-text-... and order of tags. Also good remember here that JSON object \"is an unordered set\" (not repeat keys and the keys can\'t have predefined order)... Even ZF\'s xml2json is wrong (!) because not preserve exactly the XML structure.

All solutions here have problems with this simple XML,

    <states x-x=\'1\'>
        <state y=\"123\">Alabama</state>
        My name is <b>John</b> Doe
        <state>Alaska</state>
    </states>

... @FTav solution seems better than 3-line solution, but also have little bug when tested with this XML.

Old solution is the best (for loss-less representation)

The solution, today well-known as jsonML, is used by Zorba project and others, and was first presented in ~2006 or ~2007, by (separately) Stephen McKamey and John Snelson.

// the core algorithm is the XSLT of the \"jsonML conventions\"
// see  https://github.com/mckamey/jsonml
$xslt = \'https://raw.githubusercontent.com/mckamey/jsonml/master/jsonml.xslt\';
$dom = new DOMDocument;
$dom->loadXML(\'
    <states x-x=\\\'1\\\'>
        <state y=\"123\">Alabama</state>
        My name is <b>John</b> Doe
        <state>Alaska</state>
    </states>
\');
if (!$dom) die(\"\\nERROR!\");
$xslDoc = new DOMDocument();
$xslDoc->load($xslt);
$proc = new XSLTProcessor();
$proc->importStylesheet($xslDoc);
echo $proc->transformToXML($dom);

Produce

[\"states\",{\"x-x\":\"1\"},
    \"\\n\\t    \",
    [\"state\",{\"y\":\"123\"},\"Alabama\"],
    \"\\n\\t\\tMy name is \",
    [\"b\",\"John\"],
    \" Doe\\n\\t    \",
    [\"state\",\"Alaska\"],
    \"\\n\\t\"
]

See http://jsonML.org or github.com/mckamey/jsonml. The production rules of this JSON are based on the element JSON-analog,

\"enter

This syntax is a element definition and recurrence, with
element-list ::= element \',\' element-list | element.



回答14:

$xml = simplexml_load_string($xml_string);
$json = json_encode($xml);
$array = json_decode($json,TRUE);

just add those three lines you will get the correct output:-)



回答15:

After researching a little bit all of the answers, I came up with a solution that worked just fine with my JavaScript functions across browsers (Including consoles / Dev Tools) :

<?php

 // PHP Version 7.2.1 (Windows 10 x86)

 function json2xml( $domNode ) {
  foreach( $domNode -> childNodes as $node) {
   if ( $node -> hasChildNodes() ) { json2xml( $node ); }
   else {
    if ( $domNode -> hasAttributes() && strlen( $domNode -> nodeValue ) ) {
     $domNode -> setAttribute( \"nodeValue\", $node -> textContent );
     $node -> nodeValue = \"\";
    }
   }
  }
 }

 function jsonOut( $file ) {
  $dom = new DOMDocument();
  $dom -> loadXML( file_get_contents( $file ) );
  json2xml( $dom );
  header( \'Content-Type: application/json\' );
  return str_replace( \"@\", \"\", json_encode( simplexml_load_string( $dom -> saveXML() ), JSON_PRETTY_PRINT ) );
 }

 $output = jsonOut( \'https://boxelizer.com/assets/a1e10642e9294f39/b6f30987f0b66103.xml\' );

 echo( $output );

 /*
  Or simply 
  echo( jsonOut( \'https://boxelizer.com/assets/a1e10642e9294f39/b6f30987f0b66103.xml\' ) );
 */

?>

It basically creates a new DOMDocument, loads and XML file into it and traverses through each one of the nodes and children getting the data / parameters and exporting it into JSON without the annoying \"@\" signs.

Link to the XML file.



回答16:

$templateData =  $_POST[\'data\'];

// initializing or creating array
$template_info =  $templateData;

// creating object of SimpleXMLElement
$xml_template_info = new SimpleXMLElement(\"<?xml version=\\\"1.0\\\"?><template></template>\");

// function call to convert array to xml
array_to_xml($template_info,$xml_template_info);

//saving generated xml file
 $xml_template_info->asXML(dirname(__FILE__).\"/manifest.xml\") ;

// function defination to convert array to xml
function array_to_xml($template_info, &$xml_template_info) {
    foreach($template_info as $key => $value) {
        if(is_array($value)) {
            if(!is_numeric($key)){
                $subnode = $xml_template_info->addChild($key);
                if(is_array($value)){
                    $cont = 0;
                    foreach(array_keys($value) as $k){
                        if(is_numeric($k)) $cont++;
                    }
                }

                if($cont>0){
                    for($i=0; $i < $cont; $i++){
                        $subnode = $xml_body_info->addChild($key);
                        array_to_xml($value[$i], $subnode);
                    }
                }else{
                    $subnode = $xml_body_info->addChild($key);
                    array_to_xml($value, $subnode);
                }
            }
            else{
                array_to_xml($value, $xml_template_info);
            }
        }
        else {
            $xml_template_info->addChild($key,$value);
        }
    }
}


标签: php xml json