Saya memiliki file yang menyimpan banyak objek JavaScript dalam bentuk JSON dan saya perlu membaca file tersebut, membuat setiap objek, dan melakukan sesuatu dengannya (masukkan ke dalam db dalam kasus saya). Objek JavaScript dapat direpresentasikan dalam format:
Format A:
[{name: 'thing1'},
....
{name: 'thing999999999'}]
atau Format B:
{name: 'thing1'} // <== My choice.
...
{name: 'thing999999999'}
Perhatikan bahwa ...
menunjukkan banyak objek JSON. Saya sadar saya bisa membaca seluruh file ke dalam memori dan kemudian menggunakan JSON.parse()
seperti ini:
fs.readFile(filePath, 'utf-8', function (err, fileContents) {
if (err) throw err;
console.log(JSON.parse(fileContents));
});
Namun, filenya bisa sangat besar, saya lebih suka menggunakan aliran untuk melakukannya. Masalah yang saya lihat dengan aliran adalah bahwa konten file dapat dipecah menjadi potongan data kapan saja, jadi bagaimana saya dapat menggunakannya JSON.parse()
pada objek seperti itu?
Idealnya, setiap objek akan dibaca sebagai potongan data yang terpisah, tetapi saya tidak yakin bagaimana melakukannya .
var importStream = fs.createReadStream(filePath, {flags: 'r', encoding: 'utf-8'});
importStream.on('data', function(chunk) {
var pleaseBeAJSObject = JSON.parse(chunk);
// insert pleaseBeAJSObject in a database
});
importStream.on('end', function(item) {
console.log("Woot, imported objects into the database!");
});*/
Catatan, saya ingin mencegah membaca seluruh file ke dalam memori. Efisiensi waktu tidak penting bagi saya. Ya, saya dapat mencoba membaca sejumlah objek sekaligus dan memasukkan semuanya sekaligus, tetapi itu adalah tweak kinerja - Saya memerlukan cara yang dijamin tidak menyebabkan kelebihan memori, tidak peduli berapa banyak objek yang ada di file .
Saya dapat memilih untuk menggunakan FormatA
atau FormatB
atau mungkin yang lain, harap sebutkan dalam jawaban Anda. Terima kasih!
sumber
Jawaban:
Untuk memproses file baris demi baris, Anda hanya perlu memisahkan pembacaan file dan kode yang bertindak atas input itu. Anda dapat melakukannya dengan menyangga masukan Anda sampai Anda mencapai baris baru. Dengan asumsi kita memiliki satu objek JSON per baris (pada dasarnya, format B):
Setiap kali aliran file menerima data dari sistem file, itu disimpan di buffer, dan kemudian
pump
dipanggil.Jika tidak ada baris baru di buffer,
pump
cukup kembali tanpa melakukan apa pun. Lebih banyak data (dan kemungkinan baris baru) akan ditambahkan ke buffer saat berikutnya stream mendapatkan data, dan kemudian kita akan memiliki objek yang lengkap.Jika ada baris baru,
pump
potong buffer dari awal ke baris baru dan serahkan keprocess
. Kemudian memeriksa kembali apakah ada baris baru lain di buffer (while
loop). Dengan cara ini, kami dapat memproses semua baris yang telah dibaca di bagian saat ini.Akhirnya,
process
dipanggil sekali per baris masukan. Jika ada, ini akan menghapus karakter carriage return (untuk menghindari masalah dengan akhiran baris - LF vs CRLF), dan kemudian memanggilJSON.parse
salah satu baris. Pada titik ini, Anda dapat melakukan apa pun yang Anda perlukan dengan objek Anda.Perhatikan bahwa
JSON.parse
ketat tentang apa yang diterima sebagai input; Anda harus mengutip pengenal dan nilai string Anda dengan tanda kutip ganda . Dengan kata lain,{name:'thing1'}
akan melempar kesalahan; Anda harus menggunakan{"name":"thing1"}
.Karena tidak lebih dari sepotong data yang akan ada dalam memori pada satu waktu, ini akan sangat efisien dalam menggunakan memori. Ini juga akan sangat cepat. Tes cepat menunjukkan saya memproses 10.000 baris di bawah 15ms.
sumber
Saat saya berpikir bahwa akan menyenangkan untuk menulis pengurai JSON streaming, saya juga berpikir bahwa mungkin saya harus melakukan pencarian cepat untuk melihat apakah sudah ada yang tersedia.
Ternyata ada.
Karena saya baru menemukannya, saya jelas tidak menggunakannya, jadi saya tidak dapat mengomentari kualitasnya, tetapi saya akan tertarik untuk mengetahui apakah ini berfungsi.
Itu berhasil, pertimbangkan Javascript berikut dan
_.isString
:Ini akan mencatat objek saat mereka masuk jika aliran adalah larik objek. Oleh karena itu, satu-satunya hal yang disangga adalah satu objek pada satu waktu.
sumber
Mulai Oktober 2014 , Anda hanya dapat melakukan sesuatu seperti berikut (menggunakan JSONStream) - https://www.npmjs.org/package/JSONStream
Untuk mendemonstrasikan dengan contoh kerja:
data.json:
hello.js:
sumber
parse('*')
atau Anda tidak akan mendapatkan data apa pun.var getStream() = function () {
harus dihilangkan.Saya menyadari bahwa Anda ingin menghindari membaca seluruh file JSON ke dalam memori jika memungkinkan, namun jika Anda memiliki memori yang tersedia, itu mungkin bukan ide yang buruk dari segi kinerja. Menggunakan node.js's require () pada file json memuat data ke dalam memori dengan sangat cepat.
Saya menjalankan dua tes untuk melihat seperti apa kinerja saat mencetak atribut dari setiap fitur dari file geojson 81MB.
Pada tes pertama, saya membaca seluruh file geojson ke dalam memori menggunakan
var data = require('./geo.json')
. Itu membutuhkan waktu 3330 milidetik dan kemudian mencetak atribut dari setiap fitur membutuhkan waktu 804 milidetik dengan total keseluruhan 4134 milidetik. Namun, ternyata node.js menggunakan memori 411MB.Dalam tes kedua, saya menggunakan jawaban @ arcseldon dengan JSONStream + event-stream. Saya memodifikasi kueri JSONPath untuk memilih hanya yang saya butuhkan. Kali ini memorinya tidak pernah melebihi 82MB, namun, semuanya sekarang membutuhkan 70 detik untuk diselesaikan!
sumber
Saya memiliki persyaratan serupa, saya perlu membaca file json besar di node js dan memproses data dalam potongan dan memanggil api dan simpan di mongodb. inputFile.json seperti:
Sekarang saya menggunakan JsonStream dan EventStream untuk mencapai ini secara sinkron.
sumber
Saya menulis modul yang dapat melakukan ini, yang disebut BFJ . Secara khusus, metode
bfj.match
ini dapat digunakan untuk memecah aliran besar menjadi beberapa bagian JSON yang terpisah:Di sini,
bfj.match
mengembalikan aliran mode objek yang dapat dibaca yang akan menerima item data yang diurai, dan diteruskan 3 argumen:Aliran yang dapat dibaca yang berisi masukan JSON.
Predikat yang menunjukkan item mana dari JSON yang diurai akan didorong ke aliran hasil.
Objek opsi yang menunjukkan bahwa input adalah JSON yang dibatasi baris baru (ini untuk memproses format B dari pertanyaan, tidak diperlukan untuk format A).
Setelah dipanggil,
bfj.match
akan mengurai JSON dari input stream depth-first, memanggil predikat dengan setiap nilai untuk menentukan apakah akan mendorong item itu ke aliran hasil atau tidak. Predikat diberikan tiga argumen:Kunci properti atau indeks larik (ini
undefined
untuk item tingkat atas).Nilai itu sendiri.
Kedalaman item dalam struktur JSON (nol untuk item level teratas).
Tentu saja predikat yang lebih kompleks juga bisa digunakan sesuai kebutuhan sesuai kebutuhan. Anda juga dapat meneruskan string atau ekspresi reguler sebagai ganti fungsi predikat, jika Anda ingin melakukan pencocokan sederhana terhadap kunci properti.
sumber
Saya memecahkan masalah ini menggunakan modul npm split . Pipa aliran Anda menjadi beberapa bagian, dan itu akan " Hancurkan aliran dan pasang kembali sehingga setiap baris menjadi potongan ".
Kode sampel:
sumber
Jika Anda memiliki kendali atas file input, dan itu adalah larik objek, Anda dapat menyelesaikannya dengan lebih mudah. Atur untuk mengeluarkan file dengan setiap catatan dalam satu baris, seperti ini:
Ini masih JSON yang valid.
Kemudian, gunakan modul readline node.js untuk memprosesnya satu baris dalam satu waktu.
sumber
Saya pikir Anda perlu menggunakan database. MongoDB adalah pilihan yang baik dalam hal ini karena kompatibel dengan JSON.
UPDATE : Anda dapat menggunakan alat mongoimport untuk mengimpor data JSON ke MongoDB.
sumber