Create new object and write to file with fs.writeFile() - javascript

There are two things I want to do:
I want to create a new array of objects from an existing object,
And increment the object so each object can have a count id of 1,2,3 etc
My issue is that when I write to the file it writes only 1 random object to the file and the rest don't show. There are so errors and all the objects have the same increment value. Please explain what I am doing wrong. Thanks.
Code:
data.json:
{
"users":[
{
"name":"mike",
"category":[
{
"title":"cook",
}
],
"store":{
"location":"uptown",
"city":"ulis"
},
"account":{
"type":"regular",
"payment":[
"active":false
]
}
}
]
}
index.js:
const appData = ('./data.json')
const fs = require('fs');
let newObject = {}
appData.forEach(function(items){
let x = items
let numincrement = 1++
newObject.name = x.name
newObject.count = numincrement
newObject.categories = x.categories
newObject.store = x.store
newObject.account = x.account
fs.writeFile('./temp.json', JSON.stringify(newObject, null, 2),'utf8' , function(err, data) {
// console.log(data)
if(err) {
console.log(err)
return
} else{
console.log('created')
}
})
})

There are a whole bunch of problems here:
You're just rewriting the same object over and over to the file. fs.writeFile() rewrites the entire file. It does not append to the file. In addition, you cannot append to the JSON format either. So, this code will only every write one object to the file.
To append new JSON data to what's in the existing file, you would have to read in the existing JSON, parse it to convert it to a Javascript array, then add new items onto the array, then convert back to JSON and write out the file again. For more efficient appending, you would need a different data format (perhaps comma delimited lines).
Your loop has all sorts of problems. You're assigning to the same newObject over and over again.
Your numincrement is inside the loop so it will have the same value on every invocation of the loop. You can also just use the index parameter passed to the forEach() callback instead of using your own variable.
If what you're trying to iterate over is the users array in your data, then you may need to be iterating over appData.users, not just appData.
If you really just want to append data to a text file, the JSON is not the easiest format to use. It might be easier to just use comma delimited lines. Then, you can just append new lines to the file. Can't really do that with JSON.
If you're willing to just overwrite the file with the current data, you can do this:
const appData = ('./data.json').users;
const fs = require('fs');
// create an array of custom objects
let newData = appData.map((item, index) => {
return {
name: item.name,
count: index + 1,
categories: item.categoies,
store: item.store,
account: item.account
};
});
// write out that data to a file as JSON (overwriting existing file)
fs.writeFile('./temp.json', JSON.stringify(newData, null, 2),'utf8' , function(err, data) {
if (err) {
console.log(err);
} else {
console.log("data written");
}
});

Related

Unable to add object fetched from mongoDB to normal javascript array

I am trying to create an add to cart button which fetches the data from product database using the id of specific product which I selected. I am trying to push the object found using the same Id into a normal javascript array and then to display it using ejs methods. While I was tring I found I am unable to push the data in object form.
Summary:
On 7th line I have declared an array and in that array I want to store some objects which I have fetched frome a db model.
On 15th line I am trying to push the object form into my array so that I could iterate through the objects to display them on my page using ejs. But I am unable to do that.
screenshots:
Here's the final result I'm getting even after trying to push objects in array:
empty array logged
Here are the objects I'm trying to push:
Objects
Code:
app.get("/cart", (req, res) => {
if (req.isAuthenticated()) {
const findcartdata = req.user.username;
userData.findOne({email: findcartdata}, (err, BookId) => {
// console.log(BookId.cartItemId);
const idArray = BookId.cartItemId;
var bookArray = [];
idArray.forEach((data) => {
productData.findOne({_id: data}, (err, foundBookData) =>{
// console.log(foundBookData);
if(err){
console.log(err);
}
else{
bookArray.push(foundBookData);
}
})
});
console.log(bookArray);
// res.render("cart", {
// cartBookArray: BookId.cartItemId
// })
});
} else {
res.redirect("/login");
}
})
In above code i found the user's email using passport authentication user method and using that email I wanted to add the products in a different javascript array (which I am goint to pass to my ejs file of cart and then iterate it on list) using those array of Id which I got from another model called userData. The problem is I am able to find userData of each Id but unable to store them as an array of objects.
Looks like a timing issue, your code completes before the database downloads the objects and pushes them to your array.
This should fix your issue:
// ...
const idArray = BookId.cartItemId;
var bookArray = [];
for (const data of idArray) {
const foundBookData = await productData.findOne({_id: data}).catch(console.error);
if (!foundBookData) continue;
bookArray.push(foundBookData);
}
console.log(bookArray);
// ...
By the way, make sure to make the whole function asynchronous as well, which would be done by changing this line:
userData.findOne({email: findcartdata}, async (err, BookId) => { // ...

How can we store the instance of a couple of variables once computed forever?

Right now, I have to precompute the floyd warshall cost and path matrix every time my server loads.
This is for a map which is N by N. We only have a couple of maps so I think I should precompute into variables before the server even starts up.
I have 4 variables.
Cost -> Matrix of values.
Path -> Matrix of tuples
TupleVal -> Tuple as a key mapped to a number (Map() object in JS)
IndexVal -> Number as a key mapped to a Tuple (Map() object in JS)'
How can I compute these 4 variables ONCE, and store it somewhere such that it is relatively
easy to retrieve? Should this be done through JSON? If so how can I write to a JSON file and read from a JSON file these specific datastructures?
//This is the map I use. A Tuple is converted to a string which maps to number
class ArrayKeyedMap extends Map {
get(array) {
return super.get(this.toKey(array));
}
set(array, value) {
return super.set(this.toKey(array), value);
}
has(array) {
return super.has(this.toKey(array));
}
delete(array) {
return super.delete(this.toKey(array));
}
toKey(array) {
return JSON.stringify(array);
}
}
.
.
.
// This is what I return ForbiddenVals and tupleVal are arraykeyedmap object
// index is a map object, mapping a number to a tuple. (x,y)
// path and cost are a 2 dimensional array, which contains numbers.
return [path, cost, tupleVal, index, ForbiddenVals]
Above, is basically the data structures I use. What is the easiest way to compute these values ONCE, so I never have to ever again unless I make a change to the maps?
Thank you
If you're using Express you can save the JSON to a file with fs
const fs = require('fs');
const path = require('path');
//resolve a relative path to an absolute one
const cacheDir = path.resolve('./json');
//the name of the json file, can be anything in any directory
const jsonFile = `${cacheDir}/json/n_x_n.map.json`;
let data;
//create the cache directories if they don't exist
if(!fs.existsSync(`${cacheDir}/json`)) {
fs.mkdirSync(`${cacheDir}/json`, {recursive: true});
}
//if the JSON file does not exist, generate the json and save it to the disk
if(!fs.existsSync(jsonFile)) {
data = genData(); //this is where you generate hte values once
fs.writeFile(jsonFile, JSON.stringify(data), (err) => {
if(err) {
console.error('Couldn\'t save JSON', err);
} else {
console.log('Saved JSON');
}
}
} else {
//otherwise load the JSON from the file
data = JSON.parse(fs.readFileSync(jsonFile));
}
//do whatever with the data
If it is just a website you could use localstorage
//load the data from localStorage
let data = localStorage.getItem('json');
//if there is no data in the localStorage, generate the data and save it
if(!data) {
data = genData();
localStorage.setItem('json', data);
}
//do whatever with the data

Updating Array Objects in Realm

I have a realm Organization and Ticket. An organization has many tickets. So to update organization, it looks like this:
try {
realm.write(() => {
realm.create('Organization', {
id: organization.id,
name: organization.name,
ticket: downloadTickets(organization.id)
}
, true)
})
console.log("inserting/updating ogranization")
} catch(err) {
console.warn(err)
}
The downloadTickets function returns an array of objects. This will work on my initial write. But if I want to update the array, I want to add objects to the array but not overwrite the whole array. How can I do this without getting the current values, appending it to the new ones, and returning it to the object? That seems too slow.
When you use create() with the true parameter you are overwriting the properties of the existing object. What you want is to modify the existing data.
To do this you'll have to reference the original array in some fashion. You can either iterate over the new Tickets and push() them into the array, or use concat() on the existing array and passing in the new array.
try {
realm.write(() => {
var org = realm.create('Organization', {
id: organization.id,
name: organization.name
}
, true)
// Do this
var newTickets = downloadTickets(organization.id);
for(var i = 0; i < newTickets.length; i++) {
org.ticket.push(newTickets[i]);
}
// or this
org.ticket = org.ticket.concat(downloadTickets(organization.id));
})
console.log("inserting/updating ogranization")
} catch(err) {
console.warn(err)
}
As a side note: Referencing the array does not load the whole thing into memory. The actual data of the array is only on disk until you explicitly accesses it, Realm then reads it from disk. Accessing the array to add the new Tickets is not an expensive operation.

Process large array of objects in JSON file [duplicate]

I have a file which stores many JavaScript objects in JSON form and I need to read the file, create each of the objects, and do something with them (insert them into a db in my case). The JavaScript objects can be represented a format:
Format A:
[{name: 'thing1'},
....
{name: 'thing999999999'}]
or Format B:
{name: 'thing1'} // <== My choice.
...
{name: 'thing999999999'}
Note that the ... indicates a lot of JSON objects. I am aware I could read the entire file into memory and then use JSON.parse() like this:
fs.readFile(filePath, 'utf-8', function (err, fileContents) {
if (err) throw err;
console.log(JSON.parse(fileContents));
});
However, the file could be really large, I would prefer to use a stream to accomplish this. The problem I see with a stream is that the file contents could be broken into data chunks at any point, so how can I use JSON.parse() on such objects?
Ideally, each object would be read as a separate data chunk, but I am not sure on how to do that.
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!");
});*/
Note, I wish to prevent reading the entire file into memory. Time efficiency does not matter to me. Yes, I could try to read a number of objects at once and insert them all at once, but that's a performance tweak - I need a way that is guaranteed not to cause a memory overload, not matter how many objects are contained in the file.
I can choose to use FormatA or FormatB or maybe something else, just please specify in your answer. Thanks!
To process a file line-by-line, you simply need to decouple the reading of the file and the code that acts upon that input. You can accomplish this by buffering your input until you hit a newline. Assuming we have one JSON object per line (basically, format B):
var stream = fs.createReadStream(filePath, {flags: 'r', encoding: 'utf-8'});
var buf = '';
stream.on('data', function(d) {
buf += d.toString(); // when data is read, stash it in a string buffer
pump(); // then process the buffer
});
function pump() {
var pos;
while ((pos = buf.indexOf('\n')) >= 0) { // keep going while there's a newline somewhere in the buffer
if (pos == 0) { // if there's more than one newline in a row, the buffer will now start with a newline
buf = buf.slice(1); // discard it
continue; // so that the next iteration will start with data
}
processLine(buf.slice(0,pos)); // hand off the line
buf = buf.slice(pos+1); // and slice the processed data off the buffer
}
}
function processLine(line) { // here's where we do something with a line
if (line[line.length-1] == '\r') line=line.substr(0,line.length-1); // discard CR (0x0D)
if (line.length > 0) { // ignore empty lines
var obj = JSON.parse(line); // parse the JSON
console.log(obj); // do something with the data here!
}
}
Each time the file stream receives data from the file system, it's stashed in a buffer, and then pump is called.
If there's no newline in the buffer, pump simply returns without doing anything. More data (and potentially a newline) will be added to the buffer the next time the stream gets data, and then we'll have a complete object.
If there is a newline, pump slices off the buffer from the beginning to the newline and hands it off to process. It then checks again if there's another newline in the buffer (the while loop). In this way, we can process all of the lines that were read in the current chunk.
Finally, process is called once per input line. If present, it strips off the carriage return character (to avoid issues with line endings – LF vs CRLF), and then calls JSON.parse one the line. At this point, you can do whatever you need to with your object.
Note that JSON.parse is strict about what it accepts as input; you must quote your identifiers and string values with double quotes. In other words, {name:'thing1'} will throw an error; you must use {"name":"thing1"}.
Because no more than a chunk of data will ever be in memory at a time, this will be extremely memory efficient. It will also be extremely fast. A quick test showed I processed 10,000 rows in under 15ms.
Just as I was thinking that it would be fun to write a streaming JSON parser, I also thought that maybe I should do a quick search to see if there's one already available.
Turns out there is.
JSONStream "streaming JSON.parse and stringify"
Since I just found it, I've obviously not used it, so I can't comment on its quality, but I'll be interested to hear if it works.
It does work consider the following Javascript and _.isString:
stream.pipe(JSONStream.parse('*'))
.on('data', (d) => {
console.log(typeof d);
console.log("isString: " + _.isString(d))
});
This will log objects as they come in if the stream is an array of objects. Therefore the only thing being buffered is one object at a time.
As of October 2014, you can just do something like the following (using JSONStream) - https://www.npmjs.org/package/JSONStream
var fs = require('fs'),
JSONStream = require('JSONStream'),
var getStream() = function () {
var jsonData = 'myData.json',
stream = fs.createReadStream(jsonData, { encoding: 'utf8' }),
parser = JSONStream.parse('*');
return stream.pipe(parser);
}
getStream().pipe(MyTransformToDoWhateverProcessingAsNeeded).on('error', function (err) {
// handle any errors
});
To demonstrate with a working example:
npm install JSONStream event-stream
data.json:
{
"greeting": "hello world"
}
hello.js:
var fs = require('fs'),
JSONStream = require('JSONStream'),
es = require('event-stream');
var getStream = function () {
var jsonData = 'data.json',
stream = fs.createReadStream(jsonData, { encoding: 'utf8' }),
parser = JSONStream.parse('*');
return stream.pipe(parser);
};
getStream()
.pipe(es.mapSync(function (data) {
console.log(data);
}));
$ node hello.js
// hello world
I had similar requirement, i need to read a large json file in node js and process data in chunks and call a api and save in mongodb.
inputFile.json is like:
{
"customers":[
{ /*customer data*/},
{ /*customer data*/},
{ /*customer data*/}....
]
}
Now i used JsonStream and EventStream to achieve this synchronously.
var JSONStream = require("JSONStream");
var es = require("event-stream");
fileStream = fs.createReadStream(filePath, { encoding: "utf8" });
fileStream.pipe(JSONStream.parse("customers.*")).pipe(
es.through(function(data) {
console.log("printing one customer object read from file ::");
console.log(data);
this.pause();
processOneCustomer(data, this);
return data;
}),
function end() {
console.log("stream reading ended");
this.emit("end");
}
);
function processOneCustomer(data, es) {
DataModel.save(function(err, dataModel) {
es.resume();
});
}
I realize that you want to avoid reading the whole JSON file into memory if possible, however if you have the memory available it may not be a bad idea performance-wise. Using node.js's require() on a json file loads the data into memory really fast.
I ran two tests to see what the performance looked like on printing out an attribute from each feature from a 81MB geojson file.
In the 1st test, I read the entire geojson file into memory using var data = require('./geo.json'). That took 3330 milliseconds and then printing out an attribute from each feature took 804 milliseconds for a grand total of 4134 milliseconds. However, it appeared that node.js was using 411MB of memory.
In the second test, I used #arcseldon's answer with JSONStream + event-stream. I modified the JSONPath query to select only what I needed. This time the memory never went higher than 82MB, however, the whole thing now took 70 seconds to complete!
I wrote a module that can do this, called BFJ. Specifically, the method bfj.match can be used to break up a large stream into discrete chunks of JSON:
const bfj = require('bfj');
const fs = require('fs');
const stream = fs.createReadStream(filePath);
bfj.match(stream, (key, value, depth) => depth === 0, { ndjson: true })
.on('data', object => {
// do whatever you need to do with object
})
.on('dataError', error => {
// a syntax error was found in the JSON
})
.on('error', error => {
// some kind of operational error occurred
})
.on('end', error => {
// finished processing the stream
});
Here, bfj.match returns a readable, object-mode stream that will receive the parsed data items, and is passed 3 arguments:
A readable stream containing the input JSON.
A predicate that indicates which items from the parsed JSON will be pushed to the result stream.
An options object indicating that the input is newline-delimited JSON (this is to process format B from the question, it's not required for format A).
Upon being called, bfj.match will parse JSON from the input stream depth-first, calling the predicate with each value to determine whether or not to push that item to the result stream. The predicate is passed three arguments:
The property key or array index (this will be undefined for top-level items).
The value itself.
The depth of the item in the JSON structure (zero for top-level items).
Of course a more complex predicate can also be used as necessary according to requirements. You can also pass a string or a regular expression instead of a predicate function, if you want to perform simple matches against property keys.
If you have control over the input file, and it's an array of objects, you can solve this more easily. Arrange to output the file with each record on one line, like this:
[
{"key": value},
{"key": value},
...
This is still valid JSON.
Then, use the node.js readline module to process them one line at a time.
var fs = require("fs");
var lineReader = require('readline').createInterface({
input: fs.createReadStream("input.txt")
});
lineReader.on('line', function (line) {
line = line.trim();
if (line.charAt(line.length-1) === ',') {
line = line.substr(0, line.length-1);
}
if (line.charAt(0) === '{') {
processRecord(JSON.parse(line));
}
});
function processRecord(record) {
// Process the records one at a time here!
}
I solved this problem using the split npm module. Pipe your stream into split, and it will "Break up a stream and reassemble it so that each line is a chunk".
Sample code:
var fs = require('fs')
, split = require('split')
;
var stream = fs.createReadStream(filePath, {flags: 'r', encoding: 'utf-8'});
var lineStream = stream.pipe(split());
linestream.on('data', function(chunk) {
var json = JSON.parse(chunk);
// ...
});
Using the #josh3736 answer, but for ES2021 and Node.js 16+ with async/await + AirBnb rules:
import fs from 'node:fs';
const file = 'file.json';
/**
* #callback itemProcessorCb
* #param {object} item The current item
*/
/**
* Process each data chunk in a stream.
*
* #param {import('fs').ReadStream} readable The readable stream
* #param {itemProcessorCb} itemProcessor A function to process each item
*/
async function processChunk(readable, itemProcessor) {
let data = '';
let total = 0;
// eslint-disable-next-line no-restricted-syntax
for await (const chunk of readable) {
// join with last result, remove CR and get lines
const lines = (data + chunk).replace('\r', '').split('\n');
// clear last result
data = '';
// process lines
let line = lines.shift();
const items = [];
while (line) {
// check if isn't a empty line or an array definition
if (line !== '' && !/[\[\]]+/.test(line)) {
try {
// remove the last comma and parse json
const json = JSON.parse(line.replace(/\s?(,)+\s?$/, ''));
items.push(json);
} catch (error) {
// last line gets only a partial line from chunk
// so we add this to join at next loop
data += line;
}
}
// continue
line = lines.shift();
}
total += items.length;
// Process items in parallel
await Promise.all(items.map(itemProcessor));
}
console.log(`${total} items processed.`);
}
// Process each item
async function processItem(item) {
console.log(item);
}
// Init
try {
const readable = fs.createReadStream(file, {
flags: 'r',
encoding: 'utf-8',
});
processChunk(readable, processItem);
} catch (error) {
console.error(error.message);
}
For a JSON like:
[
{ "name": "A", "active": true },
{ "name": "B", "active": false },
...
]
https.get(url1 , function(response) {
var data = "";
response.on('data', function(chunk) {
data += chunk.toString();
})
.on('end', function() {
console.log(data)
});
});
I think you need to use a database. MongoDB is a good choice in this case because it is JSON compatible.
UPDATE:
You can use mongoimport tool to import JSON data into MongoDB.
mongoimport --collection collection --file collection.json

Reading serialized array of objects from a file in node js

I have a file in which I have serialized array of objects. I need to stream the file object by object.
My question regarding nodejs stream is that, do I have to take care of my data boundaries? That is, nodejs readable stream emits line by line. So after every line, should I be checking if it is a valid JSON object?
My current implementation is something like this
f = fs.createReadStream('file.txt');
let buff = '';
f.on('data', (data) => {
buff += data;
try {
process(JSON.parse(buff));
buff = '';
} catch (e) {
return;
}
}
Is there a better alternative? Also do I need to define my own data boundaries?
Note that the objects I need to serialize are continuous. That is, they will be received over time and not once.
In your current implementation, JSON.parse will only succeed once, when the entire json is in the buff variable, as this is the only prefix of the whole file that is a valid json.
Instead, you should use oboejs, a streaming json parser. Here is how to use it:
const parser = oboe().node("!.*", node => {
console.log(node);
return oboe.drop;
});
Rx.Observable.from(`[
{"name": "foo"},
{"name": "bar"},
]`).zip(Rx.Observable.interval(10), a=>a).subscribe(char => {
// console.log(char);
parser.emit("data", char);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.0.1/Rx.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/oboe.js/2.1.3/oboe-browser.js"></script>
Or adapted to your example:
const oboe = require("oboe");
oboe(fs.createReadStream('file.txt')).node("!.*", node => {
process(node);
return oboe.drop;
});

Categories

Resources