PHP で XML をJSON に変換する

サーバー側コードを使用した Ajax の簡素化

Web 2.0 の人気が高まりつつあるなか、ブラウザー上で実行するビジネス・ロジックでデータを表現するのに便利な方法として、JSON (JavaScript Object Notation) と呼ばれる新しいデータ交換形式が浮上してきています。この記事では、XML 形式のエンタープライズ・アプリケーション・データを PHPベースのサーバー・プログラムで JSON 形式に変換した上でブラウザー・アプリケーションに送信する方法を説明します。

読者からのコメントへのフォローアップとして、リスト 6 とソース・コードのダウンロード・ファイルを更新しました。

Senthil Nathan (sen@us.ibm.com), Senior Software Engineer, IBM

Senthil NathanSenthil Nathan は、ニューヨーク州 Hawthorne にある IBM T.J. Watson Research Center のシニア・ソフトウェア・エンジニアです。 22 年間、各種エンタープライズ・アプリケーションを対象としたソフトウェアの作成に取り組んできました。現在専門としているのは、一連の話題の技術、SOA、Web サービス、Java 2 Platform、J2EE (Enterprise Edition)、PHP、Ruby On Rails、Web 2.0、そして Ajax の開発です。



Edward J Pring, Senior Software Engineer, IBM

Photo of Edward PringEdward Pring は、ニューヨーク大学でコンピューター・サイエンスの理学修士号を、スタンフォード大学で数学の理学士号を取得しています。IBM Research の一員として、オペレーティング・システム、パブリッシング・アプリケーション、メインフレーム用端末エミュレーター、パーソナル・コンピューター向けウィルス監視機能、Digital Immune System 対応のネットワーク・オートメーション、そして Web サービスの視覚化およびパフォーマンス分析など、広範な IBM 製品および技術に貢献しています。彼の特許ポートフォリオは、これらの分野すべてにまたがります。



John Morar, Senior Technical Staff Member, IBM

Photo of John MorarJohn Morar 博士は、半導体およびコンピューター・ウィルス研究の分野で豊富な経験を持ちます。科学雑誌に書いた記事は 70 を数え、デバイス処理、コンピューター・ウィルス検出、Web サービス、そして経済システムの分野では特許も取得しています。彼は現在、エンタープライズ内およびエンタープライズ間両方での Web サービスの使用に取り組む研究グループをとりまとめています。



2007年 6月 05日 (初版 2007年 1月 16日)

Ajax (Asynchronous JavaScript + XML) の出現によって、Web アプリケーション開発に新たな意気込みが生まれ、多くのアーキテクトと開発者が Web アプリケーションの作成方法を見直すようになりました。JSON (JavaScript Object Notation) とは、ブラウザー上で実行するビジネス・ロジックでデータを表現するためのデータ交換形式の 1 つで、この JSON を使用してブラウザー側の JavaScript コードでデータを直接処理する方法は多くの Ajax 開発者に選ばれています。JSON が普及するにつれ、エンタープライズ・アプリケーション・データを XML 形式ではなく JSON 形式でブラウザーに提供するミドルウェア・サーバー・プログラムもますます必要になってくるはずです。これはつまり、開発者が XML でエンコードされた既存のサーバー側エンタープライズ・データを JSON に変換してからブラウザーに送信しなければならないことを意味します。この記事では、PHP ベースのサーバー・プログラムを使用して、XML 形式のアプリケーション・データを JSON 形式に変換してからブラウザー・アプリケーションに送信する方法を紹介します。

XML の基礎

XML はマークアップの定義に関する標準です。XML ベースのマークアップを使用して記述されるデータはタグによって表現されますが、このタグは事前定義する必要がありません。このように必要に応じて新しいタグを考案できるので、XML は極めて拡張性に優れています。リスト 1 は、XML で表現したデータ構造の一例です。

リスト 1. 単純な XML データの例
<?xml version="1.0" encoding="UTF-8"?>
<contacts>
    <contact id="1">
        <name>John Doe</name>
        <phone>123-456-7890</phone>
        <address>
            <street>123 JFKStreet</street>
            <city>Any Town</city>
            <state>Any State</state>
            <zipCode>12345</zipCode>
        </address>
    </contact>
</contacts>

先頭行は、使用する XML バージョンと文字エンコード方式を指定する XML 宣言です。これに続くルート要素 <contacts> は、複数の子要素を囲んでいます。この子要素のネスト構造がまとまって、連絡先のデータを定義します。注目すべき点は、<address> 要素に含まれる子要素が <contact> 要素のサブツリー構造になっていることです。また、XML では開始タグに属性を持たせ、要素に関する追加情報を提供することもできます。上記では、<contact> 要素に含まれる属性によって id 属性が割り当てられています。XML 文書は、ルート要素に対応する終了タグ </contacts> で完了します。


JSON の基礎

JSON は XML のようなマークアップ言語ではなく、テキスト・ベースのデータ交換形式であり、JavaScript のオブジェクトと配列のデータ構文です。つまり、JSON を使うとデータを簡潔に表現できます。例えば、オブジェクトは中括弧 ({}) で囲み、配列は大括弧 ([]) で囲むなどです。JavaScript コードは、特別な構文解析や追加でデータ変換を行うことなく、JSON でエンコードされたデータをそのまま使用することができます。リスト 2 は、JSON 形式で表現したデータ構造です。

リスト 2. 単純な JSON データの例
{
   "contacts" : {
      "contact" : {
         "@attributes" : {
            "id" : "1"
         }, 
         "name" : "John Doe", 
         "phone" : "123-456-7890", 
         "address" : {
            "street" : "123 JFK Street", 
            "city" : "Any Town", 
            "state" : "Any State", 
            "zipCode" : "12345"
         }
      }
   }
}

リスト 2 の JSON の例には、リスト 1 の XML の例に示されたデータの各部分が示されていますが、その違いは、JSON が JavaScript オブジェクトおよび配列のデータ型を使用してデータをエンコードする方法です。データ構造は "contacts" という名前のプロパティーが含まれるオブジェクトで始まっていますが、このプロパティー自体がオブジェクトで、この "contacts" オブジェクトに含まれる "contact" というプロパティーも同じくオブジェクトです。"contact" オブジェクトに含まれる複数のプロパティーが、連絡先の詳細を構成しています。"contact" オブジェクトには、アドレスの詳細を記述する "address" という別の (ネストされた) オブジェクトが含まれていることに注意してください。XML の場合と同じく JSON 形式のデータも自己記述型なので、人間も機械も容易に読み取ることができます。


ブラウザー側のデータ処理

それでは、ブラウザー側のコードではデータがどのように処理されるかを見てみましょう。サーバーがブラウザーに XML データを送信すると、XML データは DOM (Document Object Model) API によって処理されます。ただし JavaScript 開発者がこのデータで何らかの操作を行うためには、XML の複雑な処理内容をすべて理解し、XML データを構文解析するための追加コードを大量に作成しなければなりません。ところが JSON の出現により、ブラウザーの要求に応答して JSON でエンコードしたアプリケーション・データをサーバー側のコードがパッケージ化して送ることが可能になりました。サーバー側のコードが JSON 形式のデータをブラウザーに送信すれば、JavaScript 開発者が追加の複雑なロジックを作成して XML を構文解析する必要はなくなります。さらに、JSON 形式で受信したデータは、ブラウザー側のコードでそのまま JavaScript 固有のデータ構造として処理されることになります。リスト 3 のコード・スニペットを見ると、JSON 形式のデータを処理するための追加作業は一切必要ないことがわかります。

リスト 3. サーバーから受信した JSON 形式のデータを処理するコード・スニペット
var result = httpRequest.responseText;
jsonResponse = eval('(' + result + ')');

リスト 3 のブラウザー側のコード・スニペットでは、result はサーバーから受信した JSON 形式のストリングを示します。必要なのは、JSON 形式のストリングを eval ステートメントで評価することによって、ストリング・データをネイティブ JavaScript データ型に変換する 1 行だけです。これを見れば、ブラウザー側で JSON データを処理するのは遥かに簡単であることがわかるはずです。JSON を使用することで、ブラウザー側のコードが簡潔で価値あるものになります。

リスト 3 の eval ステートメントは、サーバーが戻すあらゆるコードを実行するので、セキュリティー上のリスクが伴いますが、ブラウザーのセキュリティー・ポリシーにより、JavaScript の HTTP 要求はコードのロード元サーバーに制限されるためリスクは限られています。それでも状況によっては、eval ステートメントに渡す前に、正規表現などによってサーバーから受信したデータが期待したものと同じであることを検証するのが賢明な場合もあります。


XML から JSON への変換

XML データから JSON への変換が必要なアプリケーションが増えるなか、このような変換を行う Web ベースのサービスがすでにいくつか登場しています。IBM T.J. Watson Research Center では、PHP を使用して変換する特定の手法を開発しました。この手法は、XML ストリング・データを入力として受け入れ、JSON 形式のデータ出力に変換します。この PHP ベースのソリューションには、以下の利点があります。

  • コマンド・ラインから実行して、スタンドアロン・モードで動作させることができます。
  • 既存のサーバー側コード成果物に組み込むことができます。
  • Web 上の Web サービスとして簡単にホストできます。

XML から JSON への変換には、以下のコアとなる PHP 機能が必要となります。

  • SimpleXMLElement
  • http://pear.php.net の Services_JSON (詳細は「参考文献」を参照)

SimpleXMLElement は、PHP バージョン 5 以降でサポートされます。これは、XML 文書内のデータが含まれるプロパティーを持つオブジェクトです。Services_JSON は 2005年の終わり近くに承認された PHP オープン・ソースの提案で、JSON 固有のエンコーダー (PHP データから JSON へのエンコード) 関数とデコーダー (JSON から PHP データへのデコード) 関数を提供します。このオープン・ソースの PHP クラス・ライブラリーは現在、PEAR Web サイトから入手できます (「参考文献」を参照)。

上記の 2 つのコアとなる PHP 機能を使用することで、あらゆる XML データを JSON に変換することが可能になります。まず必要なのは、SimpleXMLElement を使って XML コンテンツを適切な PHP データ型に変換することです。この PHP データを Services_JSON エンコーダーに提供すれば、最終的な JSON 形式の出力になります。


PHP コードを理解する

この xml2json 実装は、以下の 3 つの部分で構成されています。

  • xml2json.php - 2 つの静的関数を持つ PHP クラス
  • xml2json_test.php - xml2json 変換関数を実行するテスト・ドライバー
  • test1.xml、test2.xml、test3.xml、test4.xml - 複雑度の異なる XML ファイル

簡潔にするため、この記事ではコードに詳細のコメントを含めていませんが、付属のソース・ファイルのコードには完全なコメントが記載されています。プログラム・ロジックの完全な詳細については、付属のソース・ファイルを参照してください (「ダウンロード」を参照)。

リスト 4 では、いくつかの便利な定数を定義しています。コードの先頭行が Services_JSON 実装をインポートします。

リスト 4. xml2json.php での定数の定義
require_once 'json/JSON.php';
// Internal program-specific Debug option.
define ("DEBUG", false);
// Maximum Recursion Depth that we can allow.
define ("MAX_RECURSION_DEPTH_ALLOWED", 25);
// An empty string
define ("EMPTY_STR", ");
// SimpleXMLElement object property name for attributes
define ("SIMPLE_XML_ELEMENT_OBJECT_PROPERTY_FOR_ATTRIBUTES", "@attributes");
// SimpleXMLElement object name.
define ("SIMPLE_XML_ELEMENT_PHP_CLASS", "SimpleXMLElement");

リスト 5 に示すコード・スニペットは、xml2json コンバーターへの入力関数です。この関数は XML データを入力として使用し、XML ストリングを SimpleXMLElement オブジェクトに変換します。SimpleXMLElement オブジェクトはこのクラスの別の (再帰的) 関数に入力として送信され、その関数によって XML 要素が PHP 連想配列に変換されます。変換された配列は次に入力として Services_JSON エンコーダーに渡され、このエンコーダーが JSON 形式の出力を提供します。

リスト 5. xml2json.php での Services_JSON の使用
public static function transformXmlStringToJson($xmlStringContents) {
    $simpleXmlElementObject = simplexml_load_string($xmlStringContents); 

if ($simpleXmlElementObject == null) { return(EMPTY_STR); }
$jsonOutput = EMPTY_STR;
// Let us convert the XML structure into PHP array structure. $array1 = xml2json::convertSimpleXmlElementObjectIntoArray($simpleXmlElementObject);
if (($array1 != null) & (sizeof($array1) > 0)) { // Create a new instance of Services_JSON $json = new Services_JSON(); // Let us now convert it to JSON formatted data. $jsonOutput = $json->encode($array1); } // End of if (($array1 != null) & (sizeof($array1) > 0))
return($jsonOutput); } // End of function transformXmlStringToJson

リスト 6 に示す長いコード・スニペットは、PHP オープン・ソース・コミュニティー (「参考文献」を参照) から発想を得た再帰手法を使用して、SimpleXMLElement オブジェクトを入力として受け入れ、ネストされた XML ツリーで再帰的ツリー・ウォークを行っています。アクセスしたすべての XML 要素は PHP 連想配列に格納されます。再帰の深さの制限は、リスト 4 で定義した定数を変更することで調整できます。

リスト 6. xml2json.php での変換ロジック
public static function convertSimpleXmlElementObjectIntoArray($simpleXmlElementObject,
&$recursionDepth=0) { 
  // Keep an eye on how deeply we are involved in recursion.

if ($recursionDepth > MAX_RECURSION_DEPTH_ALLOWED) { // Fatal error. Exit now. return(null); }
if ($recursionDepth == 0) { if (get_class($simpleXmlElementObject) != SIMPLE_XML_ELEMENT_PHP_CLASS) { // If the external caller doesn't call this function initially // with a SimpleXMLElement object, return now. return(null); } else { // Store the original SimpleXmlElementObject sent by the caller. // We will need it at the very end when we return from here for good. $callerProvidedSimpleXmlElementObject = $simpleXmlElementObject; } } // End of if ($recursionDepth == 0) {
if (get_class($simpleXmlElementObject) == SIMPLE_XML_ELEMENT_PHP_CLASS) { // Get a copy of the simpleXmlElementObject $copyOfsimpleXmlElementObject = $simpleXmlElementObject; // Get the object variables in the SimpleXmlElement object for us to iterate. $simpleXmlElementObject = get_object_vars($simpleXmlElementObject); }
// It needs to be an array of object variables. if (is_array($simpleXmlElementObject)) { // Initialize the result array. $resultArray = array(); // Is the input array size 0? Then, we reached the rare CDATA text if any. if (count($simpleXmlElementObject) <= 0) { // Let us return the lonely CDATA. It could even be // an empty element or just filled with whitespaces. return (trim(strval($copyOfsimpleXmlElementObject))); }
// Let us walk through the child elements now. foreach($simpleXmlElementObject as $key=>$value) { // When this block of code is commented, XML attributes will be // added to the result array. // Uncomment the following block of code if XML attributes are // NOT required to be returned as part of the result array. /* if((is_string($key)) & ($key == SIMPLE_XML_ELEMENT_OBJECT_PROPERTY_FOR_ATTRIBUTES)) { continue; } */
// Let us recursively process the current element we just visited. // Increase the recursion depth by one. $recursionDepth++; $resultArray[$key] = xml2json::convertSimpleXmlElementObjectIntoArray($value, $recursionDepth);
// Decrease the recursion depth by one. $recursionDepth--; } // End of foreach($simpleXmlElementObject as $key=>$value) {
if ($recursionDepth == 0) { // That is it. We are heading to the exit now. // Set the XML root element name as the root [top-level] key of // the associative array that we are going to return to the caller of this // recursive function. $tempArray = $resultArray; $resultArray = array(); $resultArray[$callerProvidedSimpleXmlElementObject->getName()] = $tempArray; }
return ($resultArray); } else { // We are now looking at either the XML attribute text or // the text between the XML tags. return (trim(strval($simpleXmlElementObject))); } // End of else } // End of function convertSimpleXmlElementObjectIntoArray.

XML ツリー・ウォークが完了すると、この関数は PHP 連想配列に含まれるすべての XML 要素 (ルート要素およびすべての子要素) を変換して格納します。複雑な XML 文書の場合は、PHP 配列も同じく複雑になります。PHP 配列が完全に構成されると、あとは Services_JSON エンコーダーで簡単にこの配列を JSON 形式のデータに変換することができます。この再帰ロジックを理解するには、文書化されたソース・ファイルを調べてください。


xml2json のテスト・ドライバーを実装する

リスト 7 のコード・スニペットは、xml2json コンバーターのロジックを実行するテスト・ドライバーです。

リスト 7. xml2json_test.php
<?php
    require_once("xml2json.php");

// Filename from where XML contents are to be read. $testXmlFile = ";
// Read the filename from the command line. if ($argc <= 1) { print("Please provide the XML filename as a command-line argument:\n"); print("\tphp -f xml2json_test.php test1.xml\n"); return; } else { $testXmlFile = $argv[1]; }
//Read the XML contents from the input file. file_exists($testXmlFile) or die('Could not find file ' . $testXmlFile); $xmlStringContents = file_get_contents($testXmlFile);
$jsonContents = "; // Convert it to JSON now. // xml2json simply takes a String containing XML contents as input. $jsonContents = xml2json::transformXmlStringToJson($xmlStringContents);
echo("JSON formatted output generated by xml2json:\n\n"); echo($jsonContents); ?>

このプログラムをコマンド・ラインから実行するには、コマンド・ラインの引数として XML ファイル名を使用します。

php -f xml2json_test.php test2.xml

コマンド・ラインから実行すると、プログラムはファイルの XML コンテンツをストリング変数に読み込みます。次に xml2json クラスの静的関数を呼び出し、JSON 形式の結果を取得します。このプログラムはコマンド・ラインから実行できるだけでなく、このソース・ファイルのロジックを変更すれば、xml2json コンバーターを SOAP (Simple Object Access Protocol) または REST (Representational State Transfer) アクセス・プロトコルによるリモート呼び出しが可能な Web サービスとして公開することもできます。このような変更が必要な場合、PHP では最小限の作業で簡単に変更できます。

リスト 8 に示すのは、この記事付属の xml2json 実装をテストするための 4 つのテスト XML ファイルのうちの 1 つです。複雑度はテスト・ファイルによって異なります。コマンド・ラインの引数として、これらのファイルのいずれかをテスト・ドライバー xml2json_test.php に渡すことができます。

リスト 8. test2.xml による xml2json 実装のテスト
<?xml version="1.0" encoding="UTF-8"?>
<books>
    <book id="1">
        <title>Code Generation in Action</title>
        <author><first>Jack</first><last>Herrington</last></author>
        <publisher>Manning</publisher>
    </book>

<book id="2"> <title>PHP Hacks</title> <author><first>Jack</first><last>Herrington</last></author> <publisher>O'Reilly</publisher> </book>
<book id="3"> <title>Podcasting Hacks</title> <author><first>Jack</first><last>Herrington</last></author> <publisher>O'Reilly</publisher> </book> </books>

リスト 9 のコード・スニペットは、test2.xml をテスト・ドライバー xml2json_test.php へのコマンド・ラインの引数として使用した場合の JSON 形式の結果です。

リスト 9. test2.xml を使用した場合の JSON 形式の結果
{
   "books" : {
      "book" : [ {
         "@attributes" : {
            "id" : "1"
         }, 
         "title" : "Code Generation in Action", 
         "author" : {
            "first" : "Jack", "last" : "Herrington"
         }, 
         "publisher" : "Manning"
      }, {
         "@attributes" : {
            "id" : "2"
         }, 
         "title" : "PHP Hacks", "author" : {
            "first" : "Jack", "last" : "Herrington"
         }, 
         "publisher" : "O'Reilly"
      }, {
         "@attributes" : {
            "id" : "3"
         }, 
         "title" : "Podcasting Hacks", "author" : {
            "first" : "Jack", "last" : "Herrington"
         }, 
         "publisher" : "O'Reilly"
      }
   ]}
}

JSON データでは、<book> 要素の XML 属性 ID は、"@attributes" オブジェクト・プロパティーとして格納され、<book> 要素はオブジェクトの配列として格納されることに注目してください。この JSON 出力は、eval ステートメントを使って JavaScript コード内でそのまま使用できます。


まとめ

Web 開発者たちの間では、JSON の勢いに弾みがつき始めたばかりにすぎません。主に JavaScript 開発者の間で見られる JSON の成功は、その簡潔さと単純さによるものです。JSON は、状況によっては XML に値する代替手段となります。この記事では、ミドルウェア・サーバー層での XML から JSON への変換の必要性を要約し、XML でエンコードされた既存のエンタープライズ・データを JSON 形式のデータとして活用することで、ブラウザー側のプログラムがデータを簡単に利用できるようになるという論理的根拠を強調しました。さらに、XML から JSON への変換を実際に実行できる PHP コードも提供しています (「ダウンロード」を参照)。

この記事に記載したソース・コードは、さまざまな方法で使用できます。例えば、スタンドアロン・ツールとして使用することも、共有クラス・ライブラリーとして既存のサーバー側プログラムで使用することも、あるいは SOAP/REST Web サービス機能として使用してエンタープライズ SOA (サービス指向アーキテクチャー) に参加することもできます。


ダウンロード

内容ファイル名サイズ
PHP code for xml2jsonx-xml2json_php.zip14KB

参考文献

学ぶために

製品や技術を入手するために

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=XML, Web development, Open source
ArticleID=246051
ArticleTitle=PHP で XML をJSON に変換する
publish-date=06052007