web-dev-qa-db-fra.com

PHP convertir XML en JSON

J'essaye de convertir xml en json en php. Si je fais une conversion simple en utilisant xml simple et json_encode, aucun des attributs du show xml.

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

Donc, j'essaye de l'analyser manuellement comme ceci.

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

et la sortie pour state est {"state":{"0":"Alabama"}} plutôt que {"state":"Alabama"}

Qu'est-ce que je fais mal?

XML:

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

Sortie:

[{"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"
}
}
}
120
Bryan Hadlock

Je l'ai compris. json_encode traite les objets différemment des chaînes. Je jette l'objet sur une chaîne et cela fonctionne maintenant.

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

Json & Array à partir de XML en 3 lignes:

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

Désolé de répondre à un ancien message, mais cet article décrit une approche relativement courte, concise et facile à maintenir. Je l'ai testé moi-même et fonctionne assez bien.

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;
    }
}
?>
33
Coreus

Je suppose que je suis un peu en retard à la fête, mais j’ai écrit une petite fonction pour accomplir cette tâche. Il prend également en charge les attributs, le contenu du texte et même si plusieurs nœuds avec le même nom de nœud sont frères.

Dislaimer: .__ Je ne suis pas un natif de PHP, alors veuillez supporter de simples erreurs.

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);
    }
}   

Exemple d'utilisation:

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

Exemple d'entrée (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>

Exemple de sortie:

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

Joli imprimé:

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

Il faut garder à l’esprit les points suivants: Plusieurs balises portant le même nom peuvent être des frères et sœurs. D'autres solutions vont très probablement supprimer tout sauf le dernier frère. Pour éviter cela, chaque nœud, même s'il n'a qu'un seul enfant, est un tableau contenant un objet pour chaque instance de la variable. (Voir plusieurs "" éléments dans l'exemple)

Même l'élément racine, dont un seul doit exister dans un document XML valide, est stocké sous forme de tableau avec un objet de l'instance, uniquement pour avoir une structure de données cohérente.

Pour pouvoir distinguer le contenu du nœud XML et les attributs XML, les attributs de chaque objet sont stockés dans "$" et le contenu dans l'enfant "_".

Edit: J'ai oublié d'afficher le résultat de votre exemple de données d'entrée

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

Un écueil courant consiste à oublier que json_encode() ne respecte pas les éléments avec un attribut textvalue et . Il en choisira une, c'est-à-dire dataloss . La fonction ci-dessous résout ce problème Si l’on décide d’utiliser la méthode json_encode/decode, la fonction suivante est conseillée.

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 ) );

de cette manière, <foo bar="3">Lorem</foo> ne se retrouvera pas sous la forme {"foo":"Lorem"} dans votre code JSON.

8
Coder Of Salvation

Essayez d'utiliser ceci

$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;

Ou 

Vous pouvez utiliser cette bibliothèque: https://github.com/rentpost/xml2array

6
Ajay Kumar

J'ai utilisé TypeConverter de Miles Johnson à cet effet. Il est installable avec Composer .

Vous pouvez écrire quelque chose comme ceci en l'utilisant:

<?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);
3
Husky

Optimisation de la réponse Antonio Max:

$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);
3
Marco Leuti

C'est une amélioration de la solution la plus votée d'Antonio Max, qui fonctionne également avec XML qui a des espaces de noms (en remplaçant les deux points par un trait de soulignement). Il a également quelques options supplémentaires (et analyse <person my-attribute='name'>John</person> correctement).

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;
}
2
TheStoryCoder

Si vous souhaitez convertir uniquement une partie spécifique du XML en JSON, vous pouvez utiliser XPath pour le récupérer et le convertir en 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);
?>

Veuillez noter que si votre Xpath est incorrect, cela mourra avec une erreur. Donc, si vous déboguez cela par le biais d'appels AJAX, je vous recommande également de consigner le corps des réponses.

1
ChrisR

Cette solution gère les espaces de noms, les attributs et produit des résultats cohérents avec des éléments récurrents (toujours dans un tableau, même s'il n'y a qu'une occurrence) . Inspiré par sxiToArray () de ratfactor }.

/**
 * <root><a>5</a><b>6</b><b>8</b></root> -> {"root":[{"a":["5"],"b":["6","8"]}]}
 * <root a="5"><b>6</b><b>8</b></root> -> {"root":[{"a":"5","b":["6","8"]}]}
 * <root xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"><a>123</a><wsp:b>456</wsp:b></root> 
 *   -> {"root":[{"xmlns:wsp":"http://schemas.xmlsoap.org/ws/2004/09/policy","a":["123"],"wsp:b":["456"]}]}
 */
function domNodesToArray(array $tags, \DOMXPath $xpath)
{
    $tagNameToArr = [];
    foreach ($tags as $tag) {
        $tagData = [];
        $attrs = $tag->attributes ? iterator_to_array($tag->attributes) : [];
        $subTags = $tag->childNodes ? iterator_to_array($tag->childNodes) : [];
        foreach ($xpath->query('namespace::*', $tag) as $nsNode) {
            // the only way to get xmlns:*, see https://stackoverflow.com/a/2470433/2750743
            if ($tag->hasAttribute($nsNode->nodeName)) {
                $attrs[] = $nsNode;
            }
        }

        foreach ($attrs as $attr) {
            $tagData[$attr->nodeName] = $attr->nodeValue;
        }
        if (count($subTags) === 1 && $subTags[0] instanceof \DOMText) {
            $text = $subTags[0]->nodeValue;
        } elseif (count($subTags) === 0) {
            $text = '';
        } else {
            // ignore whitespace (and any other text if any) between nodes
            $isNotDomText = function($node){return !($node instanceof \DOMText);};
            $realNodes = array_filter($subTags, $isNotDomText);
            $subTagNameToArr = domNodesToArray($realNodes, $xpath);
            $tagData = array_merge($tagData, $subTagNameToArr);
            $text = null;
        }
        if (!is_null($text)) {
            if ($attrs) {
                if ($text) {
                    $tagData['_'] = $text;
                }
            } else {
                $tagData = $text;
            }
        }
        $keyName = $tag->nodeName;
        $tagNameToArr[$keyName][] = $tagData;
    }
    return $tagNameToArr;
}

function xmlToArr(string $xml)
{
    $doc = new \DOMDocument();
    $doc->loadXML($xml);
    $xpath = new \DOMXPath($doc);
    $tags = $doc->childNodes ? iterator_to_array($doc->childNodes) : [];
    return domNodesToArray($tags, $xpath);
}

Exemple:

php > print(json_encode(xmlToArr('<root a="5"><b>6</b></root>')));
{"root":[{"a":"5","b":["6"]}]}
0
Artur Klesun

Si vous êtes un utilisateur d'ubuntu, installez un lecteur xml (j'ai php 5.6. Si vous en avez d'autres, trouvez le paquet et installez-le)

Sudo apt-get install php5.6-xml
service Apache2 restart

$fileContents = file_get_contents('myDirPath/filename.xml');
$fileContents = str_replace(array("\n", "\r", "\t"), '', $fileContents);
$fileContents = trim(str_replace('"', "'", $fileContents));
$oldXml = $fileContents;
$simpleXml = simplexml_load_string($fileContents);
$json = json_encode($simpleXml);
0
Atul Baldaniya

Toutes les solutions ici ont des problèmes!

... Lorsque la représentation nécessite une interprétation XML parfaite (sans problème d'attributs) et de reproduire tous les tags text-tag-text-tag-text -... et les balises. Rappelez-vous également que objet JSON "est un ensemble non ordonné" (les clés ne sont pas répétées et les clés ne peuvent pas avoir un ordre prédéfini) ... Même xml2json de ZF est faux (!) car pas préserver exactement la structure XML.

Toutes les solutions ici ont des problèmes avec ce simple XML,

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

... La solution @FTav semble meilleure qu'une solution à 3 lignes, mais présente également peu de bogues lors des tests avec ce XML.

L'ancienne solution est la meilleure (pour une représentation sans perte)

La solution, aujourd'hui connue sous le nom de jsonML, est utilisée par projet Zorba et d'autres, et a été présentée pour la première fois en ~ 2006 ou ~ 2007, par (séparément) Stephen McKamey et 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);

Produire 

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

Voir http://jsonML.org ou github.com/mckamey/jsonml . Les règles de production de ce JSON sont basées sur le JSON-analogique element

enter image description here

Cette syntaxe est une définition et une récurrence de élément, avec 
element-list ::= element ',' element-list | element.

0
Peter Krauss

Après avoir étudié un peu toutes les réponses, je suis arrivé à une solution qui fonctionnait parfaitement avec les fonctions JavaScript de tous les navigateurs (y compris les consoles/outils de développement):

<?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' ) );
 */

?>

Fondamentalement, il crée un nouveau document DOMDocument, des charges et un fichier XML, et parcourt chacun des noeuds et des enfants, récupérant les données/paramètres et les exportant au format JSON sans les signes "@" gênants.

Lien vers le fichier XML .

0
Xedret

La question ne le dit pas, mais d'habitude PHP renvoie JSON à une page Web.

Je trouve beaucoup plus facile de convertir le XML en JSON dans le navigateur/la page via une lib. JS, par exemple:

https://code.google.com/p/x2js/downloads/detail?name=x2js-v1.1.3.Zip
0
Bret Weinraub

On dirait que la variable $state->name contient un tableau. Vous pouvez utiliser

var_dump($state)

dans la foreach pour tester cela.

Si tel est le cas, vous pouvez modifier la ligne à l'intérieur de la foreach en 

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

pour le corriger.

0
Michael Fenwick
This is better solution

$fileContents= file_get_contents("https://www.feedforall.com/sample.xml");
$fileContents = str_replace(array("\n", "\r", "\t"), '', $fileContents);
$fileContents = trim(str_replace('"', "'", $fileContents));
$simpleXml = simplexml_load_string($fileContents);
$json = json_encode($simpleXml);
$array = json_decode($json,TRUE);
return $array;
0
Rashiqul Rony