Pengganti drop-in yang sangat mudah digunakan dan hemat memori untuk iterasi file atau aliran JSON besar yang tidak efisien untuk PHP >=7.2. Lihat TL;DR. Tidak ada ketergantungan dalam produksi kecuali ext-json
opsional. README sinkron dengan kode
BARU di versi 1.2.0
- Iterasi rekursif
<?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);
}
Akses acak seperti $users[42]
belum dimungkinkan. Gunakan foreach
yang disebutkan di atas dan temukan itemnya atau gunakan JSON Pointer.
Hitung item melalui iterator_count($users)
. Ingatlah bahwa ia masih harus mengulangi semuanya secara internal untuk mendapatkan penghitungan dan dengan demikian akan memakan waktu yang hampir sama dengan mengulanginya dan menghitung dengan tangan.
Memerlukan ext-json
jika digunakan langsung, tetapi tidak jika dekoder khusus digunakan. Lihat Decoder.
Ikuti CHANGELOG.
Mesin JSON adalah pengurai aliran/tarikan/tambahan/malas (apa pun namanya) JSON yang efisien, mudah digunakan, dan cepat berdasarkan generator yang dikembangkan untuk aliran atau dokumen JSON yang panjangnya tidak dapat diprediksi. Fitur utamanya adalah:
foreach
. Tidak ada acara dan panggilan balik.json_decode
asli untuk mendekode item dokumen JSON secara default. Lihat Decoder. Katakanlah fruits.json
berisi dokumen JSON yang sangat besar ini:
// fruits.json
{
"apple" : {
"color" : " red "
},
"pear" : {
"color" : " yellow "
}
}
Itu dapat diurai seperti ini:
<?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"
}
Mengurai array json alih-alih objek json mengikuti logika yang sama. Kunci dalam foreach adalah indeks numerik suatu item.
Jika Anda lebih suka Mesin JSON mengembalikan array daripada objek, gunakan new ExtJsonDecoder(true)
sebagai decoder.
<?php
use JsonMachine JsonDecoder ExtJsonDecoder ;
use JsonMachine Items ;
$ objects = Items:: fromFile ( ' path/to.json ' , [ ' decoder ' => new ExtJsonDecoder ( true )]);
Jika Anda ingin mengulangi hanya subpohon results
di fruits.json
ini :
// fruits.json
{
"results" : {
"apple" : {
"color" : " red "
},
"pear" : {
"color" : " yellow "
}
}
}
gunakan JSON Pointer /results
sebagai opsi 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"
}
Catatan:
Nilai dari
results
tidak dimuat ke dalam memori sekaligus, namun hanya satu item dalamresults
pada satu waktu. Itu selalu merupakan satu item dalam memori pada suatu waktu di level/subpohon yang sedang Anda iterasi. Dengan demikian, konsumsi memori bersifat konstan.
Spesifikasi JSON Pointer juga memungkinkan penggunaan tanda hubung ( -
) sebagai ganti indeks array tertentu. Mesin JSON menafsirkannya sebagai wildcard yang cocok dengan indeks array apa pun (bukan kunci objek apa pun). Hal ini memungkinkan Anda mengulangi nilai yang disarangkan dalam array tanpa memuat seluruh item.
Contoh:
// fruitsArray.json
{
"results" : [
{
"name" : " apple " ,
"color" : " red "
},
{
"name" : " pear " ,
"color" : " yellow "
}
]
}
Untuk mengulangi semua warna buah, gunakan JSON Pointer "/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 ' ;
}
Anda dapat mengurai satu nilai skalar di mana pun dalam dokumen dengan cara yang sama seperti koleksi. Perhatikan contoh ini:
// fruits.json
{
"lastModified" : " 2012-12-12 " ,
"apple" : {
"color" : " red "
},
"pear" : {
"color" : " yellow "
},
// ... gigabytes follow ...
}
Dapatkan nilai skalar dari kunci lastModified
seperti ini:
<?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'
}
Ketika parser menemukan nilainya dan memberikannya kepada Anda, parser berhenti menguraikan. Jadi, ketika nilai skalar tunggal berada di awal file atau aliran berukuran gigabyte, nilai tersebut akan mendapatkan nilai dari awal dalam waktu singkat dan hampir tanpa menggunakan memori.
Jalan pintas yang jelas adalah:
<?php
use JsonMachine Items ;
$ fruits = Items:: fromFile ( ' fruits.json ' , [ ' pointer ' => ' /lastModified ' ]);
$ lastModified = iterator_to_array ( $ fruits )[ ' lastModified ' ];
Akses nilai skalar tunggal juga mendukung indeks array di JSON Pointer.
Dimungkinkan juga untuk mengurai beberapa subpohon menggunakan beberapa JSON Pointer. Perhatikan contoh ini:
// 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 "
}
]
}
Untuk mengulangi semua buah beri dan buah jeruk, gunakan pointer JSON ["/berries", "/citrus"]
. Urutan petunjuk tidak menjadi masalah. Item akan diulang sesuai urutan kemunculannya di dokumen.
<?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 ' ;
}
Gunakan RecursiveItems
alih-alih Items
ketika struktur JSON sulit atau bahkan tidak mungkin ditangani dengan Items
dan pointer JSON atau masing-masing item yang Anda ulangi terlalu besar untuk ditangani. Di sisi lain, ini lebih lambat daripada Items
, jadi ingatlah itu.
Ketika RecursiveItems
menemukan daftar atau dict di JSON, ia mengembalikan instance baru yang kemudian dapat diulangi dan siklus berulang. Oleh karena itu, ia tidak pernah mengembalikan array atau objek PHP, tetapi hanya nilai skalar atau RecursiveItems
. Tidak ada dict atau daftar JSON yang akan dimuat sepenuhnya ke dalam memori sekaligus.
Mari kita lihat contoh dengan banyak pengguna dan banyak teman:
// 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 ' ;
}
}
}
}
}
Jika Anda menghentikan iterasi dari level yang lebih dalam (yaitu Anda melewatkan beberapa
"friends"
melaluibreak
) dan maju ke nilai berikutnya (yaitu nextuser
), Anda tidak akan dapat mengulanginya nanti. Mesin JSON harus mengulanginya di latar belakang agar dapat membaca nilai berikutnya. Upaya seperti itu akan menghasilkan pengecualian generator tertutup.
RecursiveItems
toArray(): array
Jika Anda yakin bahwa instance tertentu dari RecursiveItems menunjuk ke struktur data yang dapat dikelola memori (misalnya, $friend), Anda dapat memanggil $friend->toArray()
, dan item tersebut akan terwujud menjadi sebuah susunan PHP biasa.
advanceToKey(int|string $key): scalar|RecursiveItems
Saat mencari kunci tertentu dalam koleksi (misalnya, 'friends'
di $user
), Anda tidak perlu menggunakan loop dan kondisi untuk mencarinya. Sebagai gantinya, Anda cukup menelepon $user->advanceToKey("friends")
. Ini akan mengulangi untuk Anda dan mengembalikan nilai pada kunci ini. Panggilan bisa dirantai. Ini juga mendukung sintaksis seperti array untuk memajukan dan mendapatkan indeks berikut. Jadi $user['friends']
akan menjadi alias untuk $user->advanceToKey('friends')
. Panggilan bisa dirantai. Ingatlah bahwa ini hanyalah sebuah alias - Anda tidak akan dapat mengakses indeks sebelumnya secara acak setelah menggunakannya secara langsung di RecursiveItems
. Itu hanya gula sintaksis. Gunakan toArray()
jika Anda memerlukan akses acak ke indeks pada catatan/item.
Contoh sebelumnya dapat disederhanakan sebagai berikut:
<?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 ' ;
}
}
Chaining memungkinkan Anda melakukan sesuatu seperti ini:
<?php
use JsonMachine RecursiveItems
$ users = RecursiveItems:: fromFile ( ' users.json ' );
$ users [ 0 ][ ' friends ' ][ 1 ][ ' username ' ] === ' friend2 ' ;
RecursiveItems implements RecursiveIterator
Jadi, Anda dapat menggunakan misalnya alat bawaan PHP untuk mengerjakan RecursiveIterator
seperti itu:
Ini adalah cara mengatasi satu item dalam dokumen JSON. Lihat JSON Pointer RFC 6901. Ini sangat berguna, karena terkadang struktur JSON lebih dalam, dan Anda ingin mengulangi subpohon, bukan level utama. Jadi, Anda cukup menentukan penunjuk ke array atau objek JSON (atau bahkan ke nilai skalar) yang ingin Anda ulangi dan berangkatlah. Saat parser mengenai koleksi yang Anda tentukan, iterasi dimulai. Anda dapat meneruskannya sebagai opsi pointer
di semua fungsi Items::from*
. Jika Anda menentukan penunjuk ke posisi yang tidak ada dalam dokumen, pengecualian akan muncul. Ini dapat digunakan untuk mengakses nilai skalar juga. JSON Pointer sendiri harus berupa string JSON yang valid . Perbandingan literal token referensi (bagian di antara garis miring) dilakukan terhadap kunci dokumen JSON/nama anggota.
Beberapa contoh:
Nilai Penunjuk JSON | Akan mengulanginya |
---|---|
(string kosong - default) | ["this", "array"] atau {"a": "this", "b": "object"} akan diiterasi (level utama) |
/result/items | {"result": {"items": ["this", "array", "will", "be", "iterated"]}} |
/0/items | [{"items": ["this", "array", "will", "be", "iterated"]}] (mendukung indeks array) |
/results/-/status | {"results": [{"status": "iterated"}, {"status": "also iterated"}]} (tanda hubung sebagai wildcard indeks array) |
/ (gotcha! - garis miring diikuti string kosong, lihat spesifikasinya) | {"":["this","array","will","be","iterated"]} |
/quotes" | {"quotes"": ["this", "array", "will", "be", "iterated"]} |
Opsi dapat mengubah cara JSON diurai. Array opsi adalah parameter kedua dari semua fungsi Items::from*
. Opsi yang tersedia adalah:
pointer
- String JSON Pointer yang memberi tahu bagian mana dari dokumen yang ingin Anda ulangi.decoder
- Sebuah contoh antarmuka ItemDecoder
.debug
- true
atau false
untuk mengaktifkan atau menonaktifkan mode debug. Saat mode debug diaktifkan, data seperti baris, kolom, dan posisi dalam dokumen tersedia selama penguraian atau pengecualian. Membiarkan debug dinonaktifkan akan menambah sedikit keunggulan kinerja. Respons API aliran atau aliran JSON lainnya diurai dengan cara yang persis sama seperti file. Satu-satunya perbedaan adalah, Anda menggunakan Items::fromStream($streamResource)
untuk itu, di mana $streamResource
adalah sumber daya aliran dengan dokumen JSON. Selebihnya sama dengan parsing file. Berikut beberapa contoh klien http populer yang mendukung respons streaming:
Guzzle menggunakan alirannya sendiri, tetapi aliran tersebut dapat dikonversi kembali ke aliran PHP dengan memanggil GuzzleHttpPsr7StreamWrapper::getResource()
. Teruskan hasil fungsi ini ke fungsi Items::fromStream
, dan Anda sudah siap. Lihat contoh GuzzleHttp yang berfungsi.
Respons aliran Symfony HttpClient berfungsi sebagai iterator. Dan karena Mesin JSON didasarkan pada iterator, integrasi dengan Symfony HttpClient sangat sederhana. Lihat contoh HttpClient.
debug
diaktifkan) Dokumen besar mungkin memerlukan waktu beberapa saat untuk diuraikan. Panggil Items::getPosition()
di foreach
Anda untuk mendapatkan jumlah byte yang diproses saat ini dari awal. Persentasenya kemudian mudah dihitung sebagai position / total * 100
. Untuk mengetahui ukuran total dokumen Anda dalam byte, Anda mungkin ingin memeriksa:
strlen($document)
jika Anda mengurai stringfilesize($file)
jika Anda mengurai fileContent-Length
jika Anda mengurai respons aliran http Jika debug
dinonaktifkan, getPosition()
selalu mengembalikan 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 ) . ' % ' ;
}
Fungsi Items::from*
juga menerima opsi decoder
. Ini harus berupa turunan dari JsonMachineJsonDecoderItemDecoder
. Jika tidak ada yang ditentukan, ExtJsonDecoder
digunakan secara default. Diperlukan ekstensi PHP ext-json
, karena menggunakan json_decode
. Ketika json_decode
tidak melakukan apa yang Anda inginkan, terapkan JsonMachineJsonDecoderItemDecoder
dan buat sendiri.
ExtJsonDecoder
- Bawaan. Menggunakan json_decode
untuk memecahkan kode kunci dan nilai. Konstruktor memiliki parameter yang sama dengan json_decode
.
PassThruDecoder
- Tidak melakukan decoding. Kunci dan nilai dihasilkan sebagai string JSON murni. Berguna ketika Anda ingin mengurai item JSON dengan item lain secara langsung di foreach dan tidak ingin mengimplementasikan JsonMachineJsonDecoderItemDecoder
. Karena 1.0.0
tidak menggunakan json_decode
.
Contoh:
<?php
use JsonMachine JsonDecoder PassThruDecoder ;
use JsonMachine Items ;
$ items = Items:: fromFile ( ' path/to.json ' , [ ' decoder ' => new PassThruDecoder ]);
ErrorWrappingDecoder
- Dekorator yang membungkus kesalahan decoding di dalam objek DecodingError
sehingga memungkinkan Anda melewati item yang formatnya salah alih-alih mati pada pengecualian SyntaxError
. Contoh: <?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 );
}
Karena 0.4.0 setiap pengecualian diperluas JsonMachineException
, sehingga Anda dapat menangkapnya untuk memfilter kesalahan apa pun dari pustaka Mesin JSON.
Jika ada kesalahan di mana pun dalam aliran json, pengecualian SyntaxError
akan ditampilkan. Itu sangat merepotkan, karena jika ada kesalahan dalam satu item json Anda tidak dapat mengurai sisa dokumen karena satu item salah format. ErrorWrappingDecoder
adalah dekorator decoder yang dapat membantu Anda dalam hal itu. Bungkus decoder dengan itu, dan semua item salah format yang Anda ulangi akan diberikan kepada Anda di foreach melalui DecodingError
. Dengan cara ini Anda dapat melewatinya dan melanjutkan lebih jauh dengan dokumen tersebut. Lihat contoh di Dekoder yang tersedia. Kesalahan sintaksis dalam struktur aliran json antara item yang diulang akan tetap memunculkan pengecualian SyntaxError
.
Kompleksitas waktu selalu O(n)
TL;DR: Kompleksitas memori adalah O(2)
Mesin JSON membaca aliran (atau file) 1 item JSON dalam satu waktu dan menghasilkan 1 item PHP yang sesuai dalam satu waktu. Ini adalah cara yang paling efisien, karena jika Anda mengatakan 10.000 pengguna dalam file JSON dan ingin menguraikannya menggunakan json_decode(file_get_contents('big.json'))
, Anda akan memiliki seluruh string di memori serta semua 10.000 Struktur PHP. Tabel berikut menunjukkan perbedaannya:
Merangkai item dalam memori sekaligus | Item PHP yang didekodekan dalam memori sekaligus | Total | |
---|---|---|---|
json_decode() | 10.000 | 10.000 | 20.000 |
Items::from*() | 1 | 1 | 2 |
Artinya, Mesin JSON selalu efisien untuk berbagai ukuran JSON yang diproses. 100 GB tidak masalah.
TL;DR: Kompleksitas memori adalah O(n+1)
Ada juga metode Items::fromString()
. Jika Anda terpaksa mengurai string besar, dan alirannya tidak tersedia, Mesin JSON mungkin lebih baik daripada json_decode
. Alasannya adalah tidak seperti json_decode
, Mesin JSON masih melintasi string JSON satu item dalam satu waktu dan tidak memuat semua struktur PHP yang dihasilkan ke dalam memori sekaligus.
Mari lanjutkan contoh dengan 10.000 pengguna. Kali ini semuanya ada dalam string di memori. Saat mendekode string tersebut dengan json_decode
, 10.000 array (objek) dibuat di memori dan kemudian hasilnya dikembalikan. Mesin JSON di sisi lain membuat struktur tunggal untuk setiap item yang ditemukan dalam string dan mengembalikannya kepada Anda. Saat Anda memproses item ini dan beralih ke item berikutnya, struktur tunggal lainnya akan dibuat. Ini adalah perilaku yang sama dengan aliran/file. Tabel berikut menempatkan konsep tersebut ke dalam perspektif:
Merangkai item dalam memori sekaligus | Item PHP yang didekodekan dalam memori sekaligus | Total | |
---|---|---|---|
json_decode() | 10.000 | 10.000 | 20.000 |
Items::fromString() | 10.000 | 1 | 10001 |
Kenyataannya bahkan lebih baik. Items::fromString
menggunakan memori sekitar 5x lebih sedikit dibandingkan json_decode
. Alasannya adalah struktur PHP membutuhkan lebih banyak memori dibandingkan representasi JSON terkait.
Salah satu alasannya mungkin karena item yang ingin Anda ulangi ada di beberapa sub-kunci seperti "results"
tetapi Anda lupa menentukan Pointer JSON. Lihat Mengurai subpohon.
Alasan lainnya mungkin karena salah satu item yang Anda ulangi berukuran sangat besar sehingga tidak dapat didekodekan sekaligus. Misalnya, Anda mengulangi pengguna dan salah satu dari mereka memiliki ribuan objek "teman" di dalamnya. Solusi paling efisien adalah dengan menggunakan iterasi Rekursif.
Ini mungkin berarti bahwa satu string skalar JSON itu sendiri terlalu besar untuk dimasukkan ke dalam memori. Misalnya file berkode base64 yang sangat besar. Dalam hal ini Anda mungkin masih kurang beruntung sampai Mesin JSON mendukung menghasilkan nilai skalar sebagai aliran PHP.
composer require halaxa/json-machine
Kloning atau unduh repositori ini dan tambahkan yang berikut ini ke file bootstrap Anda:
spl_autoload_register ( require ' /path/to/json-machine/src/autoloader.php ' );
Kloning repositori ini. Perpustakaan ini mendukung dua pendekatan pengembangan:
Jalankan composer run -l
di direktori proyek untuk melihat skrip dev yang tersedia. Dengan cara ini Anda dapat menjalankan beberapa langkah proses pembangunan seperti pengujian.
Instal Docker dan jalankan make
di direktori proyek pada mesin host Anda untuk melihat alat/perintah pengembang yang tersedia. Anda dapat menjalankan semua langkah proses pembangunan secara terpisah serta seluruh proses pembangunan sekaligus. Make pada dasarnya menjalankan skrip pengembang komposer di dalam container di latar belakang.
make build
: Menjalankan build lengkap. Perintah yang sama dijalankan melalui GitHub Actions CI.
Apakah Anda menyukai perpustakaan ini? Beri bintang, bagikan, tunjukkan :) Masalah dan permintaan penarikan sangat diterima.
Apache 2.0
Elemen roda gigi: Ikon yang dibuat oleh TutsPlus dari www.flaticon.com dilisensikan oleh CC 3.0 BY
Daftar isi dibuat dengan markdown-toc