I am building a website with NodeJS which asks for a data-file to be uploaded, then I have to check and (if needed) transform the content of this file.
The source file is a JSON or XML configuration file, I just need to ensure its content is well-formatted for the rest of the application.
I am wondering what would be the best way to check the global file's content.
I usually manipulate files with Streams, but I am not sure if it allows me to do what I want...
The source file has a similar format :
{
"parameters": [{
"name": "name",
"settings": {
"key": "value"
}
}],
"data": [{
"id": "1",
"label": "label 1",
}, {
"id": "2",
"label": "label 2"
}]
}
What I need to do is to parse the file's content, and check if the file-format is good ;
Otherwise transform the file to a well-formatted one :
// Read the file content
var parameters = [],
data = [],
p = parameters.length,
d = data.length;
// Loop on the parameters, and check the format
while (p--) {
var parameter = parameters[p];
if (name in parameter && typeof parameter.name == "string") {
// Add several rules
parameters.push(parameter);
}
}
// Do a similar control for "data".
// Then save the well-formatted parameters and data into a file
The thing is that the uploaded file might be very large...
Can I perform it with transform Streams ? Because I need to check the full-content of the file as a object !
How can I be sure a Stream transformer won't give a chunk with just a part of data, for instance ?
I'd first try something like this:
var fs = require('fs');
try {
var inputFile = require('./config.json');
} catch (e) {
console.log(e.message); // Do proper error handling.
}
// Loop on the parameters, and check the format
if (!'parameters' in inputFile) {
console.log("Got a problem here!");
}
var parameters = [];
var p = inputFile['parameters'].length;
while(p--) {
var parameter = inputFile['parameters'][p];
if ('name' in parameter && typeof parameter.name == 'string') {
// Add several rules
parameters.push(parameter);
}
}
// Do a similar control for "data".
var data = inputFile['data'];
// More code needed here...
// Then save the well-formatted parameters and data into a file
fs.writeFileSync('./data.json', JSON.stringify({parameters: parameters, data: data}, null, 4) , 'utf-8');
If you are dealing with mammoth files that cannot fit into memory, you've got a HUGELY more difficult task ahead of you. In general, you cannot guarantee that a partial read will give you enough of the JSON to parse anything out of (e.g. {"data": ["<FOUR PETABYTE STRING>"]}).
Related
So I have this array with different links
const urls = ['myurl.com', 'localhost.com'... etc]
In the for loop I want to create an object like so, it should basically create a new object for every URL, and then pass the looped URL to the userUrl section
for(let url of urls) {
[
{
user: 1,
isVerified: true
userUrl: url
}
]
}
After the loop finishes, this data should be readable in a JSON file
it should look something like this
[
{
user: 1,
isVerified: true,
userUrl: myUrl.com
},
{
user: 2,
isVerified: true,
userUrl: localhost.com
}
...etc
]
I tried this code on Chrome and it works correctly plus it correctly format the json data now, instructing JSON.stringify to use 4 spaces indentation.
It won't work in the snippet, but it will if you save it in your own file. I did it inside the chrome developer tools in the Sources tab as a snippet and as soon as executed the download queue was fed with the json file.
I left the live snippet here because there's the chance to see the jsonData on console anyway.
The way to programmatically send a string to a download file was inspired by this question:
How do I save JSON to local text file
function download(content, fileName, contentType) {
var a = document.createElement("a");
var file = new Blob([content], {type: contentType});
a.href = URL.createObjectURL(file);
a.download = fileName;
a.click();
}
const urls = ['myurl.com', 'localhost.com'];
const data = factory(urls);
const jsonData = JSON.stringify(data, null, 4);
console.log(jsonData);
download(jsonData, 'json.txt', 'text/plain');
function factory(urls){
const output = [];
for(let url of urls) {
output.push({
user: 1,
isVerified: true,
userUrl: url
});
}
return output;
}
Basically, I am setting up a web server via Node.js and Express (I am a beginner at this) to retrieve data by reading a JSON file.
For example, this is my data.json file:
[{
"color": "black",
"category": "hue",
"type": "primary"
},
{
"color": "red",
"category": "hue",
"type": "primary"
}
]
I am trying to retrieve all of the colors by implementing this code for it to display on localhost:
router.get('/colors', function (req, res) {
fs.readFile(__dirname + '/data.json', 'utf8', function (err, data) {
data = JSON.parse(data);
res.json(data); //this displays all of the contents of data.json
})
});
router.get('/colors:name', function (req, res) {
fs.readFile(__dirname + '/data.json', 'utf8', function (err, data) {
data = JSON.parse(data);
for (var i = 0; i < data.length; i++) {
res.json(data[i][1]); //trying to display the values of color
}
})
});
How do I go about doing this?
What you are trying to do is actually pretty simple once you break it into smaller problems. Here is one way to break it down:
Load your JSON data into memory for use by your API.
Define an API route which extracts only the colours from your JSON data and sends them to the client as a JSON.
var data = [];
try {
data = JSON.parse(fs.readFileSync('/path/to/json'));
} catch (e) {
// Handle JSON parse error or file not exists error etc
data = [{
"color": "black",
"category": "hue",
"type": "primary"
},
{
"color": "red",
"category": "hue",
"type": "primary"
}
]
}
router.get('/colors', function (req, res, next) {
var colors = data.map(function (item) {
return item.color
}); // This will look look like: ["black","red"]
res.json(colors); // Send your array as a JSON array to the client calling this API
})
Some improvements in this method:
The file is read only once synchronously when the application is started and the data is cached in memory for future use.
Using Array.prototype.map Docs to extract an array of colors from the object.
Note:
You can structure the array of colors however you like and send it down as a JSON in that structure.
Examples:
var colors = data.map(function(item){return {color:item.color};}); // [{"color":"black"},{"color":"red"}]
var colors = {colors: data.map(function(item){return item.color;})} // { "colors" : ["black" ,"red"] }
Some gotchas in your code:
You are using res.json in a for loop which is incorrect as the response should only be sent once. Ideally, you would build the JS object in the structure you need by iterating over your data and send the completed object once with res.json (which I'm guessing internally JSON.stringifys the object and sends it as a response after setting the correct headers)
Reading files is an expensive operation. If you can afford to read it once and cache that data in memory, it would be efficient (Provided your data is not prohibitively large - in which case using files to store info might be inefficient to begin with)
in express, you can do in this way
router.get('/colors/:name', (req, res) => {
const key = req.params.name
const content = fs.readFileSync(__dirname + '/data.json', 'utf8')
const data = JSON.parse(content)
const values = data.reduce((values, value) => {
values.push(value[key])
return values
}, [])
// values => ['black', 'red']
res.send(values)
});
and then curl http://localhost/colors/color,
you can get ['black', 'red']
What you're looking to do is:
res.json(data[i]['color']);
If you don't really want to use the keys in the json you may want to use the Object.values function.
...
data = JSON.parse(data)
var values = []
for (var i = 0; i < data.length; i++) {
values.push(Object.values(data[i])[0]) // 0 - color, 1 - category, 2 - type
}
res.json(values) // ["black","red"]
...
You should never use fs.readFileSync in production. Any sync function will block the event loop until the execution is complete hence delaying everything afterwords (use with caution if deemed necessary). A few days back I had the worst experience myself and learnt that in a hard way.
In express you can define a route with param or query and use that to map the contents inside fs.readFile callback function.
/**
* get color by name
*
* #param {String} name name of the color
* #return {Array} array of the color data matching param
*/
router.get('/colors/:name', (req, res) => {
const color = req.params.name
const filename = __dirname + '/data.json';
fs.readFile('/etc/passwd', 'utf8', (err, data) => {
if(err){
return res.send([]); // handle any error returned by readFile function here
}
try{
data = JSON.parse(data); // parse the JSON string to array
let filtered = []; // initialise empty array
if(data.length > 0){ // we got an ARRAY of objects, right? make your check here for the array or else any map, filter, reduce, forEach function will break the app
filtered = data.filter((obj) => {
return obj.color === color; // return the object if the condition is true
});
}
return res.send(filtered); // send the response
}
catch(e){
return res.send([]); // handle any error returned from JSON.parse function here
}
});
});
To summarise, use fs.readFile asynchronous function so that the event loop is not clogged up. Inside the callback parse through the content and then return the response. return is really important or else you might end up getting Error: Can't set headers after they are sent
DISCLAIMER This code above is untested but should work. This is just to demonstrate the idea.
I think you can’t access JSON without key. You can use Foreach loop for(var name : object){} check about foreach it may help you
I have a JSON file that contains multiple objects of the same structure that look like this:
{
"id": "123",
"type": "alpha"
}
{
"id": "321",
"type": "beta"
}
I'm using node.js to read the file.
fs.readFile(__dirname + "/filename.json", 'utf8', function(err, data) {
var content = JSON.parse(JSON.stringify(data));
If I do a console.log(content) things look good. I see the content of the json file. I'm trying to iterate over each object but I'm not sure how to do that. I've tried using
for(var doc in content)
but the doc isn't each object as I was expecting. How do I loop over the content to get each object in a json format so that I can parse it?
If content is an array, you can use
content.forEach(function (obj, index) { /* your code */ })
See documentation for Array.prototype.forEach()
if you need to just iterate, a forEach loop would work or a normal for loop :
for(var i = 0; i<content.length(); i++){
//perform whatever you need on the following object
var myobject = content[i];
}
Depend of the files, the two current answer (Osama and Daniel) assume you have a JSON Array:
[
{
"id": "123",
"type": "alpha"
},
{
"id": "456",
"type": "beta"
}
]
In which case, you can use any array iterator:
var async = require('async'),
content = require(__dirname + "/filename.json");
async.each(content, function (item, callback) {
//...
});
But in your case, it seems to not be JSON (no bracket to indicate array, and no comma to separate the objects), so in the case JSON.parse doesn t throw up any error, you'll need to isolate your objects first:
var fs = require('fs'),
async = require('async');
fs.readFile(__dirname + "/filename.notjson", 'utf8', function(err, data) {
var content = data.split('}');
async.map(content, function (item, callback) {
callback(null, JSON.parse(item));
}, function (err, content) {
console.log(content);
};
});
I am trying to use Map datatype in dynamodb to insert my JSON object. The JSON I am getting from external API is bit long and got nested Array of objects in it. (I am using nodejs.)
{
"a": "foo",
"b": "foo1",
"c": [{
"boo": 10,
"boo1": 15
}, {
"boo": 19,
"boo1": 45
}, {
"boo": 11,
"boo1": 25
}]
}
From the research i made so far it looks like i have to specify types for every single element in the json i am trying to insert/update. It make it harder since in my case the json could have anything.
If anyone experienced the same issue and know any solution for it please let me know.
You need to specify the exact types for every value only if you use the low-level AmazonDB API.
But you can use an AWS SDK which makes things much easier and you can use the json notation directly.
I didn't use node.js SDK (have experience with python SDK), but looking at the examples, this is true for node.js too.
Check this one:
var AWS = require("aws-sdk");
AWS.config.update({
region: "us-west-2",
endpoint: "http://localhost:8000"
});
var docClient = new AWS.DynamoDB.DocumentClient();
var table = "Movies";
var year = 2015;
var title = "The Big New Movie";
var params = {
TableName:table,
Item:{
"year": year,
"title": title,
"info":{
"plot":"Something happens."
}
}
};
console.log("Adding a new item...");
docClient.put(params, function(err, data) {
if (err) {
console.error("Unable to add item. Error JSON:", JSON.stringify(err, null, 2));
} else {
console.log("Added item:", JSON.stringify(data, null, 2));
}
});
Here the Item inside the params passed into the docClient.put call is just a plain javascript object.
It's necessary for dynamodb to know what type of data your are inserting/updating. So dynamodb or any db allows only
datatype that is specific to that column. If you looking to parse json(with Jackson api) to specific type and insert. Below is the code
JsonParser parser = new JsonFactory().createParser(new File(
"target/resources/json_file.json"));
JsonNode rootNode = new ObjectMapper().readTree(parser);
Iterator<JsonNode> iter = rootNode.iterator();
ObjectNode currentNode;
while (iter.hasNext()) {
currentNode = (ObjectNode) iter.next();
table.putItem(new Item().withPrimaryKey("a", currentNode.path("a").asText(), "b",
currentNode.path("b").asText()).withJSON("c",currentNode.path("c").toString()));
}
I'm able to successfully connect and query data from a mySql db via a Breeze/Angular client, following the todo-angular example. I switched out the db table and the GUI and was still ok. The problem starts when I try to use a naming convention. (I don't have control over the db that I have to connect to & I really don't want to use Uppercase_Underscored_Words in my client!)
I'm getting the following exception:
/Users/Sherri/Sites/awdb-web/node_modules/breeze-sequelize/node_modules/breeze-client/breeze.debug.js:1852
throw new Error("Unable to locate a registered object by the name: " + k
^
Error: Unable to locate a registered object by the name: NamingConvention.underscoreCamelCase
at Object.__config._fetchObject (/Users/Sherri/Sites/awdb-web/node_modules/breeze-sequelize/node_modules/breeze-client/breeze.debug.js:1852:13)
at MetadataStore.proto.importMetadata (/Users/Sherri/Sites/awdb-web/node_modules/breeze-sequelize/node_modules/breeze-client/breeze.debug.js:6517:40)
at new module.exports.MetadataMapper (/Users/Sherri/Sites/awdb-web/node_modules/breeze-sequelize/MetadataMapper.js:19:8)
at SequelizeManager.importMetadata (/Users/Sherri/Sites/awdb-web/node_modules/breeze-sequelize/SequelizeManager.js:46:24)
at createSequelizeManager (/Users/Sherri/Sites/awdb-web/server/routes.js:114:8)
at /Users/Sherri/Sites/awdb-web/server/routes.js:23:27
When I take the "namingConvention": "camelCase" line out of the metadata.json file, the error goes away, but of course, the database property is not able to be correctly converted.
Here is the relevant code I use to set up the Entity Manager: (EDIT: I'm pretty sure my problem is server side and has nothing to do with this code, though)
var namingConvention = new UnderscoreCamelCaseConvention();
namingConvention.setAsDefault();
breeze.core.config.initializeAdapterInstance("uriBuilder", "json");
var serviceName = 'breeze/awdb';
var manager = new breeze.EntityManager(serviceName);
// Take any server property name and make it camelCase for the client to use.
// also, save it so that we can convert from the client back to the server's name
function UnderscoreCamelCaseConvention() {
var serverNames = {
netPoints: 'netPoints',
netPointsSpent: 'netPointsSpent'
}; // every translated server name
return new breeze.NamingConvention({
name: 'underscoreCamelCase',
clientPropertyNameToServer: clientPropertyNameToServer,
serverPropertyNameToClient: serverPropertyNameToClient
});
function clientPropertyNameToServer(clientPropertyName) {
return serverNames[clientPropertyName];
}
function serverPropertyNameToClient(serverPropertyName) {
var clientName = _.camelCase(serverPropertyName);
serverNames[clientName] = serverPropertyName;
return clientName;
}
}
And here is a snippet of my metadata.json file:
{
"metadataVersion": "1.0.5",
"namingConvention": "underscoreCamelCase",
"localQueryComparisonOptions": "caseInsensitiveSQL",
"dataServices": [
{
"serviceName": "breeze/awdb/",
"hasServerMetadata": true,
"jsonResultsAdapter": "webApi_default",
"useJsonp": false
}
],
"structuralTypes": [
{
"shortName": "person",
"namespace": "AWdb.Models",
"autoGeneratedKeyType": "Identity",
"defaultResourceName": "people",
"dataProperties": [
{
"name": "Person_ID",
"dataType": "Int32",
"isNullable": false,
"defaultValue": 0,
"isPartOfKey": true,
"validators": [
{
"name": "required"
},
{
"min": -2147483648,
"max": 2147483647,
"name": "int32"
}
]
},
{
"name": "Household_ID",
"dataType": "Int32",
"validators": [
{
"min": -2147483648,
"max": 2147483647,
"name": "int32"
}
]
},
....
]
}
],
"resourceEntityTypeMap": {"people": "person:#AWdb.Models"}
}
EDIT:
Here is code from my routes.js file that gets the metadata.
var fs = require('fs');
var breezeSequelize = require('breeze-sequelize');
var SequelizeManager = breezeSequelize.SequelizeManager;
var SequelizeQuery = breezeSequelize.SequelizeQuery;
var SequelizeSaveHandler = breezeSequelize.SequelizeSaveHandler;
var breeze = breezeSequelize.breeze;
var EntityQuery = breeze.EntityQuery;
var dbConfig = {
host: 'localhost',
user: 'xx',
password: 'xx',
dbName: 'xx'
};
var _sequelizeManager = createSequelizeManager();
// _sequelizeManager.sync(true).then(seed).then(function(){
// console.log('db init successful');
// });
exports.init = init;
function init(app) {
app.get('/breeze/awdb/Metadata', function (req, res, next) {
try {
var metadata = readMetadata();
res.send(metadata);
} catch(e){
next(e);
}
});
function createSequelizeManager() {
var metadata = readMetadata();
var sm = new SequelizeManager(dbConfig);
sm.importMetadata(metadata);
return sm;
}
function readMetadata() {
var filename = "server/AWdbMetadata.json";
if (!fs.existsSync(filename)) {
filename = "AWdbMetadata.json";
if (!fs.existsSync(filename)) {
throw new Error("Unable to locate file: " + filename);
}
}
var metadata = fs.readFileSync(filename, 'utf8');
return JSON.parse(metadata);
}
Any ideas? Should I be able to use a custom naming convention when I'm on a node.js server, using a metadata.json file instead of a .net entity framework?
If I'm looking at this correctly, then I think your issue is the metadata on the server. If I understand correctly, your table and column names follow the Uppercase_Underscored_Word pattern. The Breeze/Sequelize stack on the server currently doesn't have the ability to convert names, so you must use the names of entities and properties exactly as they are in the DB schema. Otherwise, the Breeze to Sequelize translation will fail. You can still use a naming convention on the client to turn the underscored server names into whatever you want them to be on the client.
So, you need two metadata files. One for the server that is used by the Breeze/Sequelize stack and that uses names exactly as they are in the DB and then a separate metadata file for the client, where you can do the translation.