非常に使いやすく、PHP 7.2 以上の大きな JSON ファイルまたはストリームの非効率な反復をメモリ効率の高いドロップインで置き換えます。 TL;DRを参照してください。オプションのext-json
を除き、本番環境には依存関係はありません。コードと同期した README
バージョン1.2.0
の新機能 - 再帰的反復
<?php
use JsonMachineItems;
// this often causes Allowed Memory Size Exhausted,
// because it loads all the items in the JSON into memory
- $users = json_decode(file_get_contents('500MB-users.json'));
// this has very small memory footprint no matter the file size
// because it loads items into memory one by one
+ $users = Items::fromFile('500MB-users.json');
foreach ($users as $id => $user) {
// just process $user as usual
var_dump($user->name);
}
$users[42]
のようなランダム アクセスはまだ不可能です。上記のforeach
使用して項目を検索するか、JSON ポインターを使用します。
iterator_count($users)
を介して項目をカウントします。カウントを取得するには内部で全体を反復する必要があるため、反復して手動でカウントするのとほぼ同じ時間がかかることに注意してください。
そのまま使用する場合はext-json
が必要ですが、カスタム デコーダーを使用する場合は必要ありません。 「デコーダ」を参照してください。
チェンジログに従ってください。
JSON Machine は、予測できないほど長い JSON ストリームまたはドキュメント用に開発されたジェネレーターに基づいた、効率的で使いやすく、高速な JSON ストリーム/プル/インクリメンタル/遅延 (名前は何でも) パーサーです。主な機能は次のとおりです。
foreach
使用して任意のサイズの JSON を反復するだけです。イベントやコールバックはありません。json_decode
使用して JSON ドキュメント項目をデコードします。 「デコーダ」を参照してください。fruits.json
に次の巨大な JSON ドキュメントが含まれているとします。
// fruits.json
{
"apple" : {
"color" : " red "
},
"pear" : {
"color" : " yellow "
}
}
これは次のように解析できます。
<?php
use JsonMachine Items ;
$ fruits = Items:: fromFile ( ' fruits.json ' );
foreach ( $ fruits as $ name => $ data ) {
// 1st iteration: $name === "apple" and $data->color === "red"
// 2nd iteration: $name === "pear" and $data->color === "yellow"
}
json オブジェクトの代わりに json 配列を解析する場合も、同じロジックに従います。 foreach のキーは項目の数値インデックスになります。
JSON Machine がオブジェクトではなく配列を返すようにしたい場合は、デコーダーとしてnew ExtJsonDecoder(true)
を使用します。
<?php
use JsonMachine JsonDecoder ExtJsonDecoder ;
use JsonMachine Items ;
$ objects = Items:: fromFile ( ' path/to.json ' , [ ' decoder ' => new ExtJsonDecoder ( true )]);
このfruits.json
のresults
サブツリーのみを反復したい場合:
// fruits.json
{
"results" : {
"apple" : {
"color" : " red "
},
"pear" : {
"color" : " yellow "
}
}
}
JSON ポインター/results
をpointer
オプションとして使用します。
<?php
use JsonMachine Items ;
$ fruits = Items:: fromFile ( ' fruits.json ' , [ ' pointer ' => ' /results ' ]);
foreach ( $ fruits as $ name => $ data ) {
// The same as above, which means:
// 1st iteration: $name === "apple" and $data->color === "red"
// 2nd iteration: $name === "pear" and $data->color === "yellow"
}
注記:
results
の値は一度にメモリにロードされるのではなく、一度にresults
内の 1 つの項目だけがロードされます。現在反復しているレベル/サブツリーでは、メモリ内に一度に 1 つの項目が常に存在します。したがって、メモリ消費量は一定です。
JSON ポインター仕様では、特定の配列インデックスの代わりにハイフン ( -
) を使用することもできます。 JSON Machine は、これを任意の配列インデックス(オブジェクト キーではない) に一致するワイルドカードとして解釈します。これにより、項目全体をロードせずに、配列内のネストされた値を反復処理できるようになります。
例:
// fruitsArray.json
{
"results" : [
{
"name" : " apple " ,
"color" : " red "
},
{
"name" : " pear " ,
"color" : " yellow "
}
]
}
果物のすべての色を反復するには、JSON ポインター"/results/-/color"
を使用します。
<?php
use JsonMachine Items ;
$ fruits = Items:: fromFile ( ' fruitsArray.json ' , [ ' pointer ' => ' /results/-/color ' ]);
foreach ( $ fruits as $ key => $ value ) {
// 1st iteration:
$ key == ' color ' ;
$ value == ' red ' ;
$ fruits -> getMatchedJsonPointer () == ' /results/-/color ' ;
$ fruits -> getCurrentJsonPointer () == ' /results/0/color ' ;
// 2nd iteration:
$ key == ' color ' ;
$ value == ' yellow ' ;
$ fruits -> getMatchedJsonPointer () == ' /results/-/color ' ;
$ fruits -> getCurrentJsonPointer () == ' /results/1/color ' ;
}
ドキュメント内の任意の場所にある単一のスカラー値を、コレクションと同じ方法で解析できます。次の例を考えてみましょう。
// fruits.json
{
"lastModified" : " 2012-12-12 " ,
"apple" : {
"color" : " red "
},
"pear" : {
"color" : " yellow "
},
// ... gigabytes follow ...
}
次のようにlastModified
キーのスカラー値を取得します。
<?php
use JsonMachine Items ;
$ fruits = Items:: fromFile ( ' fruits.json ' , [ ' pointer ' => ' /lastModified ' ]);
foreach ( $ fruits as $ key => $ value ) {
// 1st and final iteration:
// $key === 'lastModified'
// $value === '2012-12-12'
}
パーサーは値を見つけてそれを渡すと、解析を停止します。そのため、単一のスカラー値がギガバイトサイズのファイルまたはストリームの先頭にある場合、メモリをほとんど消費せずに、すぐに先頭から値を取得します。
明らかなショートカットは次のとおりです。
<?php
use JsonMachine Items ;
$ fruits = Items:: fromFile ( ' fruits.json ' , [ ' pointer ' => ' /lastModified ' ]);
$ lastModified = iterator_to_array ( $ fruits )[ ' lastModified ' ];
単一スカラー値アクセスは、JSON ポインターの配列インデックスもサポートします。
複数の JSON ポインターを使用して複数のサブツリーを解析することもできます。次の例を考えてみましょう。
// fruits.json
{
"lastModified" : " 2012-12-12 " ,
"berries" : [
{
"name" : " strawberry " , // not a berry, but whatever ...
"color" : " red "
},
{
"name" : " raspberry " , // the same ...
"color" : " red "
}
],
"citruses" : [
{
"name" : " orange " ,
"color" : " orange "
},
{
"name" : " lime " ,
"color" : " green "
}
]
}
すべてのベリーと柑橘類を反復処理するには、JSON ポインター["/berries", "/citrus"]
を使用します。ポインタの順序は関係ありません。項目はドキュメント内で出現する順序で繰り返されます。
<?php
use JsonMachine Items ;
$ fruits = Items:: fromFile ( ' fruits.json ' , [
' pointer ' => [ ' /berries ' , ' /citruses ' ]
]);
foreach ( $ fruits as $ key => $ value ) {
// 1st iteration:
$ value == [ " name " => " strawberry " , " color " => " red " ];
$ fruits -> getCurrentJsonPointer () == ' /berries ' ;
// 2nd iteration:
$ value == [ " name " => " raspberry " , " color " => " red " ];
$ fruits -> getCurrentJsonPointer () == ' /berries ' ;
// 3rd iteration:
$ value == [ " name " => " orange " , " color " => " orange " ];
$ fruits -> getCurrentJsonPointer () == ' /citruses ' ;
// 4th iteration:
$ value == [ " name " => " lime " , " color " => " green " ];
$ fruits -> getCurrentJsonPointer () == ' /citruses ' ;
}
JSON 構造をItems
や JSON ポインターで処理することが困難または不可能な場合、または反復する個々の項目が大きすぎて処理できない場合は、 Items
の代わりにRecursiveItems
を使用します。一方で、 Items
よりも著しく遅いため、その点に留意してください。
RecursiveItems
は、JSON 内のリストまたは辞書に遭遇すると、それ自体の新しいインスタンスを返し、それを反復処理してサイクルを繰り返します。したがって、PHP 配列やオブジェクトは返されず、スカラー値またはRecursiveItems
のみが返されます。 JSON 辞書やリストが一度に完全にメモリにロードされることはありません。
非常に多くの友人を持つ非常に多くのユーザーの例を見てみましょう。
// users.json
[
{
"username" : " user " ,
"e-mail" : " [email protected] " ,
"friends" : [
{
"username" : " friend1 " ,
"e-mail" : " [email protected] "
},
{
"username" : " friend2 " ,
"e-mail" : " [email protected] "
}
]
}
]
<?php
use JsonMachine RecursiveItems
$ users = RecursiveItems:: fromFile ( ' users.json ' );
foreach ( $ users as $ user ) {
/** @var $user RecursiveItems */
foreach ( $ user as $ field => $ value ) {
if ( $ field === ' friends ' ) {
/** @var $value RecursiveItems */
foreach ( $ value as $ friend ) {
/** @var $friend RecursiveItems */
foreach ( $ friend as $ friendField => $ friendValue ) {
$ friendField == ' username ' ;
$ friendValue == ' friend1 ' ;
}
}
}
}
}
このような怠惰なより深いレベルの反復を中断し (つまり、
break
によって一部の"friends"
をスキップする)、次の値 (つまり、次のuser
) に進むと、後でそれを反復することはできません。 JSON マシンは、次の値を読み取ることができるように、バックグラウンドでそれを反復する必要があります。このような試みはクローズドジェネレータ例外を引き起こします。
RecursiveItems
の便利なメソッドtoArray(): array
RecursiveItems の特定のインスタンスがメモリ管理可能なデータ構造 ($friend など) を指していることが確実な場合は、 $friend->toArray()
を呼び出すことができ、項目は具体化されます。プレーンな PHP 配列。
advanceToKey(int|string $key): scalar|RecursiveItems
コレクション内の特定のキー (たとえば、 $user
の'friends'
) を検索する場合、ループや条件を使用して検索する必要はありません。代わりに、単に$user->advanceToKey("friends")
呼び出すことができます。反復処理が行われ、このキーの値が返されます。通話は連鎖させることができます。また、次のインデックスに進んで取得するための配列のような構文もサポートしています。したがって、 $user['friends']
$user->advanceToKey('friends')
のエイリアスになります。通話は連鎖させることができます。これは単なるエイリアスであることに注意してください。これをRecursiveItems
で直接使用した後は、以前のインデックスにランダムにアクセスできなくなります。それは単なる構文糖です。レコード/項目のインデックスにランダムにアクセスする必要がある場合は、 toArray()
を使用します。
したがって、前の例は次のように単純化できます。
<?php
use JsonMachine RecursiveItems
$ users = RecursiveItems:: fromFile ( ' users.json ' );
foreach ( $ users as $ user ) {
/** @var $user RecursiveItems */
foreach ( $ user [ ' friends ' ] as $ friend ) { // or $user->advanceToKey('friends')
/** @var $friend RecursiveItems */
$ friendArray = $ friend -> toArray ();
$ friendArray [ ' username ' ] === ' friend1 ' ;
}
}
チェーンを使用すると、次のようなことが可能になります。
<?php
use JsonMachine RecursiveItems
$ users = RecursiveItems:: fromFile ( ' users.json ' );
$ users [ 0 ][ ' friends ' ][ 1 ][ ' username ' ] === ' friend2 ' ;
RecursiveItems implements RecursiveIterator
したがって、たとえば PHP の組み込みツールを使用して、次のようなRecursiveIterator
操作できます。
これは、JSON ドキュメント内の 1 つの項目をアドレス指定する方法です。 JSON Pointer RFC 6901 を参照してください。JSON 構造がさらに深くなり、メイン レベルではなくサブツリーを反復したい場合があるため、これは非常に便利です。したがって、反復する JSON 配列またはオブジェクト (またはスカラー値) へのポインターを指定するだけで、すぐに実行できます。パーサーが指定したコレクションにヒットすると、反復が開始されます。すべてのItems::from*
関数でpointer
オプションとして渡すことができます。ドキュメント内に存在しない位置へのポインターを指定すると、例外がスローされます。スカラー値へのアクセスにも使用できます。 JSON ポインター自体は有効な JSON 文字列である必要があります。参照トークン (スラッシュ間の部分) のリテラル比較が、JSON ドキュメントのキー/メンバー名に対して実行されます。
いくつかの例:
JSONポインタ値 | 反復処理します |
---|---|
(空の文字列 - デフォルト) | ["this", "array"] または{"a": "this", "b": "object"} が反復されます (メイン レベル) |
/result/items | {"result": {"items": ["this", "array", "will", "be", "iterated"]}} |
/0/items | [{"items": ["this", "array", "will", "be", "iterated"]}] (配列インデックスをサポート) |
/results/-/status | {"results": [{"status": "iterated"}, {"status": "also iterated"}]} (配列インデックスのワイルドカードとしてのハイフン) |
/ (注意! - スラッシュの後に空の文字列が続きます。仕様を参照してください) | {"":["this","array","will","be","iterated"]} |
/quotes" | {"quotes"": ["this", "array", "will", "be", "iterated"]} |
オプションにより、JSON の解析方法が変更される場合があります。オプションの配列は、すべてのItems::from*
関数の 2 番目のパラメーターです。利用可能なオプションは次のとおりです。
pointer
- ドキュメントのどの部分を反復処理するかを示す JSON ポインター文字列。decoder
- ItemDecoder
インターフェイスのインスタンス。debug
- デバッグ モードを有効または無効にするtrue
またはfalse
。デバッグ モードが有効な場合、ドキュメント内の行、列、位置などのデータは、解析中または例外時に使用できます。デバッグを無効のままにすると、パフォーマンスがわずかに向上します。 ストリーム API 応答またはその他の JSON ストリームは、ファイルとまったく同じ方法で解析されます。唯一の違いは、 Items::fromStream($streamResource)
を使用することです。 $streamResource
は、JSON ドキュメントを含むストリーム リソースです。残りはファイルの解析と同じです。ストリーミング応答をサポートする一般的な http クライアントの例をいくつか示します。
Guzzle は独自のストリームを使用しますが、 GuzzleHttpPsr7StreamWrapper::getResource()
呼び出すことで PHP ストリームに変換し直すことができます。この関数の結果をItems::fromStream
関数に渡すと、準備は完了です。 GuzzleHttp の動作例を参照してください。
Symfony HttpClient のストリーム応答はイテレータとして機能します。また、JSON Machine はイテレーターに基づいているため、Symfony HttpClient との統合は非常に簡単です。 HttpClient の例を参照してください。
debug
有効な場合)大きなドキュメントの場合は、解析に時間がかかる場合があります。 foreach
でItems::getPosition()
を呼び出して、先頭からの現在の処理バイト数を取得します。パーセンテージは、 position / total * 100
として簡単に計算できます。ドキュメントの合計サイズをバイト単位で確認するには、次のことを確認してください。
strlen($document)
filesize($file)
Content-Length
http ヘッダーdebug
が無効な場合、 getPosition()
常に0
返します。
<?php
use JsonMachine Items ;
$ fileSize = filesize ( ' fruits.json ' );
$ fruits = Items:: fromFile ( ' fruits.json ' , [ ' debug ' => true ]);
foreach ( $ fruits as $ name => $ data ) {
echo ' Progress: ' . intval ( $ fruits -> getPosition () / $ fileSize * 100 ) . ' % ' ;
}
Items::from*
関数はdecoder
オプションも受け入れます。これはJsonMachineJsonDecoderItemDecoder
のインスタンスである必要があります。何も指定されていない場合は、デフォルトでExtJsonDecoder
が使用されます。 json_decode
を使用するため、 ext-json
PHP 拡張機能が存在する必要があります。 json_decode
希望どおりに動作しない場合は、 JsonMachineJsonDecoderItemDecoder
実装して独自に作成します。
ExtJsonDecoder
-デフォルト。 json_decode
を使用してキーと値をデコードします。コンストラクターにはjson_decode
と同じパラメーターがあります。
PassThruDecoder
- デコードを行いません。キーと値は両方とも純粋な JSON 文字列として生成されます。 foreach 内で JSON 項目を他の何かで直接解析する必要があり、 JsonMachineJsonDecoderItemDecoder
実装したくない場合に便利です。 1.0.0
以降はjson_decode
を使用しません。
例:
<?php
use JsonMachine JsonDecoder PassThruDecoder ;
use JsonMachine Items ;
$ items = Items:: fromFile ( ' path/to.json ' , [ ' decoder ' => new PassThruDecoder ]);
ErrorWrappingDecoder
- DecodingError
オブジェクト内のデコード エラーをラップするデコレーター。これにより、 SyntaxError
例外で終了する代わりに、不正な形式の項目をスキップできるようになります。例: <?php
use JsonMachine Items ;
use JsonMachine JsonDecoder DecodingError ;
use JsonMachine JsonDecoder ErrorWrappingDecoder ;
use JsonMachine JsonDecoder ExtJsonDecoder ;
$ items = Items:: fromFile ( ' path/to.json ' , [ ' decoder ' => new ErrorWrappingDecoder ( new ExtJsonDecoder ())]);
foreach ( $ items as $ key => $ item ) {
if ( $ key instanceof DecodingError || $ item instanceof DecodingError) {
// handle error of this malformed json item
continue ;
}
var_dump ( $ key , $ item );
}
0.4.0 以降、すべての例外はJsonMachineException
を拡張するため、それをキャッチして JSON Machine ライブラリからエラーをフィルタリングできます。
json ストリーム内のどこかにエラーがある場合、 SyntaxError
例外がスローされます。これは非常に不便です。1 つの JSON 項目内にエラーがあると、1 つの不正な形式の項目が原因でドキュメントの残りの部分を解析できなくなるからです。 ErrorWrappingDecoder
、それを助けるデコーダ デコレータです。デコーダをラップすると、反復処理中のすべての不正な項目がDecodingError
を介して foreach に渡されます。こうすることで、それらをスキップしてドキュメントをさらに進めることができます。 「利用可能なデコーダ」の例を参照してください。ただし、反復された項目間の JSON ストリームの構造に構文エラーがある場合は、 SyntaxError
例外がスローされます。
時間計算量は常にO(n)
です
TL;DR: メモリの複雑さはO(2)
JSON Machine は、ストリーム (またはファイル) を一度に 1 つの JSON アイテムずつ読み取り、対応する 1 つの PHP アイテムを一度に生成します。これは最も効率的な方法です。JSON ファイルにたとえば 10,000 人のユーザーがいて、 json_decode(file_get_contents('big.json'))
を使用してそれを解析したい場合、10,000 のすべての文字列だけでなく文字列全体もメモリ内に存在することになるからです。 PHP の構造。次の表に違いを示します。
メモリ内の文字列項目を一度に取得する | メモリ内で一度にデコードされた PHP 項目 | 合計 | |
---|---|---|---|
json_decode() | 10000 | 10000 | 20000 |
Items::from*() | 1 | 1 | 2 |
これは、JSON Machine が処理される JSON のサイズに関係なく常に効率的であることを意味します。 100GBでも問題ありません。
TL;DR: メモリの複雑さはO(n+1)
です
Items::fromString()
メソッドもあります。大きな文字列を解析する必要があり、ストリームが利用できない場合は、 json_decode
よりも JSON Machine の方が優れている可能性があります。その理由は、 json_decode
とは異なり、JSON Machine は JSON 文字列を一度に 1 項目ずつ走査し、結果として得られるすべての PHP 構造を一度にメモリにロードしないためです。
10,000 人のユーザーの例を続けてみましょう。今回は、それらはすべてメモリ内の文字列内にあります。その文字列をjson_decode
でデコードすると、10,000 個の配列 (オブジェクト) がメモリ内に作成され、結果が返されます。一方、JSON Machine は、文字列内で見つかった項目ごとに単一の構造を作成し、それを返します。この項目を処理して次の項目を繰り返すと、別の単一の構造が作成されます。これはストリーム/ファイルの場合と同じ動作です。次の表は、概念を大局的に示したものです。
メモリ内の文字列項目を一度に取得する | メモリ内で一度にデコードされた PHP 項目 | 合計 | |
---|---|---|---|
json_decode() | 10000 | 10000 | 20000 |
Items::fromString() | 10000 | 1 | 10001 |
現実はさらに優れています。 Items::fromString
json_decode
よりも約5 倍少ないメモリを消費します。その理由は、PHP 構造は、対応する JSON 表現よりもはるかに多くのメモリを必要とするためです。
理由の 1 つは、反復処理する項目が"results"
などのサブキー内にあるのに、JSON ポインターを指定するのを忘れたことが考えられます。 「サブツリーの解析」を参照してください。
もう 1 つの理由は、反復処理する項目の 1 つ自体が非常に大きいため、一度にデコードできないことが考えられます。たとえば、ユーザーを反復処理すると、そのうちの 1 人に何千もの「友達」オブジェクトが含まれているとします。最も効率的な解決策は、再帰的反復を使用することです。
おそらく、単一の JSON スカラー文字列自体が大きすぎてメモリに収まらないことを意味します。たとえば、base64 でエンコードされた非常に大きなファイルです。その場合、JSON Machine が PHP ストリームとしてスカラー値の生成をサポートするまでは、おそらくまだ運が悪いでしょう。
composer require halaxa/json-machine
このリポジトリのクローンを作成またはダウンロードし、ブートストラップ ファイルに以下を追加します。
spl_autoload_register ( require ' /path/to/json-machine/src/autoloader.php ' );
このリポジトリのクローンを作成します。このライブラリは、次の 2 つの開発アプローチをサポートしています。
プロジェクト ディレクトリでcomposer run -l
を実行すると、利用可能な開発スクリプトが表示されます。これにより、テストなどのビルド プロセスのいくつかのステップを実行できます。
Docker をインストールし、ホスト マシン上のプロジェクト ディレクトリでmake
実行して、利用可能な開発ツール/コマンドを確認します。ビルド プロセスのすべてのステップを個別に実行することも、ビルド プロセス全体を一度に実行することもできます。 Make は基本的に、コンポーザー開発スクリプトをコンテナー内でバックグラウンドで実行します。
make build
: 完全なビルドを実行します。同じコマンドが GitHub Actions CI 経由で実行されます。
この図書館は好きですか?スターを付け、共有し、表示してください:) 問題やプルリクエストは大歓迎です。
アパッチ2.0
歯車要素: www.flaticon.com の TutsPlus によって作成されたアイコンは、CC 3.0 BY によってライセンスされています。
markdown-toc で生成された目次