Gulp Yaml Front Matter to JSON add File Name - json

I'm not sure what is the best way to go about this.
I would like to get the yaml front matter from a markdown file convert it to json while adding the name of the file and then combine them in a single array json file.
E.g. the files bananas.md and apples.md,
---
title: Bananas
type: yellow
count:
- 1
- 2
---
# My Markdown File
apples.md:
---
title: Apples
type: red
count:
- 3
- 4
---
# My Markdown File 2
converts to all.json:
[{"title":"Bananas","type":"yellow","count":[1,2],"file":"bananas"},
{"title":"Apples","type":"red","count":[3,4],"file":"apples"}]
Of course, there wouldn't be a return as it would be compact.
I've found some gulp plugins but it doesn't seem any of them do exactly what I need, even combined, unless I'm missing something.

Update, I created the plugin gulp-pluck which vastly simplifies the process.
Here's how it works:
var gulp = require('gulp');
var data = require('gulp-data');
var pluck = require('gulp-pluck');
var frontMatter = require('gulp-front-matter');
gulp.task('front-matter-to-json', function(){
return gulp.src('./posts/*.md')
.pipe(frontMatter({property: 'meta'}))
.pipe(data(function(file){
file.meta.path = file.path;
}))
.pipe(pluck('meta', 'posts-metadata.json'))
.pipe(data(function(file){
file.contents = new Buffer(JSON.stringify(file.meta))
}))
.pipe(gulp.dest('dist'))
})
End Update
OK, took the time to figure this out. Gulp needs a built-in reduce function! (Maybe I'll work on that some time.)
Dependencies include: gulp, gulp-front-matter, gulp-filter, event-stream, stream-reduce, and gulp-rename.
Written in LiveScript:
gulp.task 'concatYaml' ->
devDest = './dev/public/'
gulp.src './src/posts/*.md'
.pipe filter posted
.pipe front-matter {property: 'meta'}
.pipe es.map (file, cb) ->
file.meta.name = path.basename file.path
file.meta.url = toUrlPath file.meta.name
cb null, file
.pipe reduce ((acc, file) ->
| acc =>
acc.meta.push file.meta
acc
| _ =>
acc = file
acc.meta = [file.meta]
acc
), void
.pipe es.map (file, cb) ->
file.contents = new Buffer JSON.stringify file.meta
cb null, file
.pipe rename 'posts.json'
.pipe gulp.dest devDest
And the JavaScript equivalent:
gulp.task('concatYaml', function(){
var devDest;
devDest = './dev/public/';
return gulp.src('./src/posts/*.md')
.pipe(filter(posted))
.pipe(frontMatter({ property: 'meta' }))
.pipe(es.map(function(file, cb){
file.meta.name = path.basename(file.path);
file.meta.url = toUrlPath(file.meta.name);
return cb(null, file);
}))
.pipe(reduce(function(acc, file){
switch (false) {
case !acc:
acc.meta.push(file.meta);
return acc;
default:
acc = file;
acc.meta = [file.meta];
return acc;
}
}, void 8))
.pipe(es.map(function(file, cb){
file.contents = new Buffer(JSON.stringify(file.meta));
return cb(null, file);
}))
.pipe(rename('posts.json'))
.pipe(gulp.dest(devDest));
});

Related

CSV File object to JSON in Angular

On the front end of my app I wanted to parse some data related to a CSV they upload. Through the file upload tool, I first get a FileList object and then pull the 1 file out of it.
I want to turn it into a json object which I could then iterate. I was thinking to user csv-parser from node, but I dont see a way to leverage a File object stored in memory.
How Can I accomplish this?
At first I was doing:
let f = fileList.item(0);
let decoder = new window.TextDecoder('utf-8');
f.arrayBuffer().then( data => {
let _data = decoder.decode(data)
console.log("Dataset", data, _data)
});
And that was passing the array buffer, and decoding the string. While I Could write a generic tool which process this string data based on \n and ',' I wanted this to be a bit more easier to read.
I wanted to do something like:
let json = csvParser(f)
is there a way to user csv-parser from node, (3.0.0) or is there another tool i should leverage? I was thinking that levering modules based on the browser ( new window.TextDecoder(...) ) is poor form since it has the opportunity to fail.
Is there a tool that does this? im trying to create some sample data and given a File picked from an input type="file" i would want to have this be simple and straight forward.
This example below works, but i feel the window dependancy and a gut feeling makes me think this is naive.
const f : File = fileList.item(0)
console.log("[FOO] File", f)
let decoder = new window.TextDecoder('utf-8');
f.arrayBuffer().then( data => {
let _data = decoder.decode(data)
console.log("Dataset", data, _data)
let lines = _data.split("\n")
let headers = lines[0].split(',')
let results = []
for ( let i = 1; i < lines.length; i++) {
let line = lines[i]
let row = {}
line.split(",").forEach( (item, idx) => {
row[headers[idx]] = item;
})
results.push(row)
}
console.log("JSON ARRAY", results)
})
The issue i run when i stop and do: ng serve is that it does not like using the arrayBuffer function and accessing TextDecoder from window, since that thost functions/classes are not a part of File and window respectively during build.
Any thoughts?
This is what I ended up doing, given the file input being passed into this function:
updateTranscoders(project: Project, fileList: FileList, choice: string = 'replace') {
const f: File = fileList.item(0)
//Reads a File into a string.
function readToString(file) : Promise<any> {
const reader = new FileReader();
const future = new Promise( (resolve,reject) => {
reader.addEventListener("load", () => {
resolve(reader.result);
}, false)
reader.addEventListener("error", (event) => {
console.error("ERROR", event)
reject(event)
}, false)
reader.readAsText(file)
});
return future;
}
readToString(f).then( data => {
let lines = data.split("\n")
let headers = lines[0].split(',')
let results = []
for (let i = 1; i < lines.length; i++) {
let line = lines[i]
let row = {}
line.split(",").forEach((item, idx) => {
row[headers[idx]] = item;
})
results.push(row)
}
if (choice.toLowerCase() === 'replace'){
let rows = project.csvListContents.toJson().rows.filter( row => row.isDeployed)
rows.push( ...results)
project.csvListContents = CsvDataset.fromJson({ rows: rows })
}else if (choice.toLowerCase() === 'append') {
let r = project.csvListContents.toJson();
r.rows.push(...results);
project.csvListContents = CsvDataset.fromJson(r);
}else {
alert("Invalid option for Choice.")
}
this.saveProject(project)
})
}
Now the CHOICE portion of the code is where I have a binary option to do a hard replace on CSV contents or just append to it. I would then save the project accordingly. This is also understanding that the first row contains column headers.

.dat (unknown notation) to json format

I am working in an open-source game called Argentum Online, you can check out our code here https://github.com/ao-libre
The problem I am facing is it has a lot of files with the extension .dat with this format:
[NPC12]
Name=Sastre
Desc=¡Hola forastero! Soy el Sastre de Ullathorpe, Bienvenido!
Head=9
Body=50
Heading=3
Movement=1
Attackable=0
Comercia=1
TipoItems=3
Hostil=0
GiveEXP=0
GiveGLD=0
InvReSpawn=0
NpcType=0
Alineacion=0
DEF=0
MaxHit=0
MaxHp=0
[NPC19]
Name=Sastre
Desc=¡Bienvenida Viajera! Tengo hermosas vestimentas para ofrecerte...
Head=70
Body=80
Heading=3
Movement=1
Attackable=0
Comercia=1
TipoItems=3
Hostil=0
GiveEXP=0
GiveGLD=0
I would like to know if this kind of parsing has a proper name and what is a good way to convert it to json?
After read the comment above from #mx0.
The format is called INI Format
https://en.wikipedia.org/wiki/INI_file
Here an answer for the transformation between INI to JSON
Npm Module:
https://github.com/npm/ini
Or
ES6 Snippet
let ini2Obj = {};
const keyValuePair = kvStr => {
const kvPair = kvStr.split('=').map( val => val.trim() );
return { key: kvPair[0], value: kvPair[1] };
};
const result = document.querySelector("#results");
document.querySelector( '#inifile' ).textContent
.split( /\n/ ) // split lines
.map( line => line.replace( /^\s+|\r/g, "" ) ) // cleanup whitespace
.forEach( line => { // convert to object
line = line.trim();
if ( line.startsWith('#') || line.startsWith(';') ) { return false; }
if ( line.length ) {
if ( /^\[/.test(line) ) {
this.currentKey = line.replace(/\[|\]/g,'');
ini2Obj[this.currentKey] = {};
} else if ( this.currentKey.length ) {
const kvPair = keyValuePair(line);
ini2Obj[this.currentKey][kvPair.key] = kvPair.value;
}
}
}, {currentKey: ''} );
result.textContent +=
`**Check: ini2Obj['Slave_Settings:11'].ConfigurationfilePath = ${
ini2Obj['Slave_Settings:11'].ConfigurationfilePath}`;
result.textContent +=
`\n\n**The converted object (JSON-stringified)\n${
JSON.stringify(ini2Obj, null, ' ')}`;
Original answer:
Javascript library for convert .ini file to .json file (client side)

webpack : is there a good hook / custom function to dump out resolved configuration?

I'm somewhat of a newbie with webpack and have been experimenting with easier ways to adjust/merge webpack configurations.
The following code, added to webpack/lib/webpack.js has been pretty helpful:
this is the standard webpack.js:
function webpack(options, callback) {
var compiler;
if(Array.isArray(options)) {
compiler = new MultiCompiler(options.map(function(options) {
return webpack(options);
}));
} else if(typeof options === "object") {
new WebpackOptionsDefaulter().process(options);
compiler = new Compiler();
compiler.options = options;
compiler.options = new WebpackOptionsApply().process(options, compiler);
new NodeEnvironmentPlugin().apply(compiler);
compiler.applyPlugins("environment");
compiler.applyPlugins("after-environment");
} else {
throw new Error("Invalid argument: options");
}
if(callback) {
if(typeof callback !== "function") throw new Error("Invalid argument: callback");
if(options.watch === true || (Array.isArray(options) &&
options.some(function(o) {
return o.watch;
}))) {
var watchOptions = (!Array.isArray(options) ? options : options[0]).watchOptions || {};
// TODO remove this in next major version
var watchDelay = (!Array.isArray(options) ? options : options[0]).watchDelay;
if(watchDelay) {
console.warn("options.watchDelay is deprecated: Use 'options.watchOptions.aggregateTimeout' instead");
watchOptions.aggregateTimeout = watchDelay;
}
return compiler.watch(watchOptions, callback);
}
compiler.run(callback);
}
this is my code:
//customization start
fs = require('fs');
var fnp_dump = 'webpack.dump.json';
fs.writeFile(fnp_dump, JSON.stringify(options, null, 2), function(err) {
if(err) {
return console.log(err);
}
console.log("dumping dump.webpack.js.final.json from webpack.js to: " + fnp_dump);
});
//customization end
return compiler;
}
The basic idea is that it dumps out the final json/js options object after webpack has finished sorting out the usual webpack.base.js + webpack.development.js. Since it's, at that point, just a fully-resolved javascript object, it doesn't really matter how the config.js files were written by individual developers.
Now you can diff options sent to webpack (this is an example of tranforming webpack 1 to webpack 2 configurations:
diff 003/webpack.dump.json 004/webpack.dump.json
< "loader": "style!css!postcss-loader!sass"
---
> "use": [
> {
> "loader": "style-loader"
> },
> {
> "loader": "postcss-loader"
> },
> {
> "loader": "sass-loader"
> }
> ]
However, I am customizing webpack.js directly and need to re-apply my patch after each npm update webpack. Is there a better way?
If your webpack.config.js is a function, you can call it on your own to resolve to an object.
If you have several configs (you mentioned webpack.base.js and webpack.development.js) you can use Webpack Merge to just combine your options to a single object, and then write it to the file system.
I would recommend you to have an own script in package.json to do this job, which you can then always call after your webpack job:
...,
"scripts": {
"dump-options": "scriptThatMergesConfigsAndWritesToFS.js",
"webpack-job": "webpack ... && npm run dump-options",
...
},
...
UPDATE
After some more research I realized, that the resolved options object is stored in the compiler object. The compiler object is passed to Plugins, and therefore you can easily write a Plugin that writes the config to a file, as I did here (not tested).
I also realized, that the Plugins cannot be stringified, as they are functions, so be aware of losing the Plugin configuration information.
I ended up writing my own plugin (and now notice that wtho wrote one too). It worked for me - note you need to have the bit of code that handles circular references:
// WebPackCompilationObserver.js
function WebPackCompilationObserver(options) {
WebPackCompilationObserver.options = options || {};
}
WebPackCompilationObserver.prototype.apply = function(compiler) {
compiler.plugin("emit", function(compilation, callback) {
var fs = require('fs');
var fnp_dump = WebPackCompilationObserver.options.dump_filename;
if (! fnp_dump) {
fnp_dump = "./dump.webpack.options.json";
console.log("please specify dump_filename path in the WebPackCompilationObserver.options, otherwise using default:" % fnp_dump);
}
if (fnp_dump){
console.log("dumping compilation.options to: " + fnp_dump);
var cache = [];
fs.writeFile(fnp_dump, JSON.stringify(compilation.options, function(key, value) {
if (typeof value === 'object' && value !== null) {
if (cache.indexOf(value) !== -1) {
// Circular reference found, discard key
return;
}
// Store value in our collection
cache.push(value);
}
return value;
}, 2),
function(err) {
if (err) {
return console.log(err);
}
});
cache = null;
}
callback();
});
};
module.exports = WebPackCompilationObserver;
To use it:
webpack.config.development.js:
....
var WebPackCompilationObserver = require("./WebPackCompilationObserver");
....
config.plugins = config.plugins.concat([
....
,new WebPackCompilationObserver({dump_filename: '../dumpithere.json'})
])

Node JS: Make a flat json from a tree json

I was writing a node.js script to combine all the json files in a directory and store the result as a new json file. I tried do the job to a great extent but it has few flaws.
A.json
[
{
"id": "addEmoticon1",
"description": "Message to greet the user.",
"defaultMessage": "Hello, {name}!"
},
{
"id": "addPhoto1",
"description": "How are youu.",
"defaultMessage": "How are you??"
}
]
B.json
[
{
"id": "close1",
"description": "Close it.",
"defaultMessage": "Close!"
}
]
What I finally need is:
result.json
{
"addEmoticon1": "Hello, {name}!",
"addPhoto1": "How are you??",
"close1": "Close!"
}
I wrote a node.js script:
var fs = require('fs');
function readFiles(dirname, onFileContent, onError) {
fs.readdir(dirname, function(err, filenames) {
if (err) {
onError(err);
return;
}
filenames.forEach(function(filename) {
fs.readFile(dirname + filename, 'utf-8', function(err, content) {
if (err) {
onError(err);
return;
}
onFileContent(filename, content);
});
});
});
}
var data = {};
readFiles('C:/node/test/', function(filename, content) {
data[filename] = content;
var lines = content.split('\n');
lines.forEach(function(line) {
var parts = line.split('"');
if (parts[1] == 'id') {
fs.appendFile('result.json', parts[3]+': ', function (err) {});
}
if (parts[1] == 'defaultMessage') {
fs.appendFile('result.json', parts[3]+',\n', function (err) {});
}
});
}, function(err) {
throw err;
});
It extracts the 'id' and 'defaultMessage' but is not able to append correctly.
What I get:
result.json
addEmoticon1: addPhoto1: Hello, {name}!,
close1: How are you??,
Close!,
This output is different every time I run my script.
Aim 1: Surround items in double quotes,
Aim 2: Add curly braces at the top and at the end
Aim 3: No comma at the end of last element
Aim 4: Same output every time I run my script
I'll start with the finished solution...
There's a big explanation at the end of this answer. Let's try to think big-picture for a little bit first tho.
readdirp('.')
.fmap(filter(match(/\.json$/)))
.fmap(map(readfilep))
.fmap(map(fmap(JSON.parse)))
.fmap(concatp)
.fmap(flatten)
.fmap(reduce(createMap)({}))
.fmap(data=> JSON.stringify(data, null, '\t'))
.fmap(writefilep(resolve(__dirname, 'result.json')))
.then(filename=> console.log('wrote results to %s', filename), err=>console.error(err));
Console output
wrote results to /path/to/result.json
result.json (I added a c.json with some data to show that this works with more than 2 files)
{
"addEmoticon1": "Hello, {name}!",
"addPhoto1": "How are you??",
"close1": "Close!",
"somethingelse": "Something!"
}
Implementation
I made Promise-based interfaces for readdir and readFile and writeFile
import {readdir, readFile, writeFile} from 'fs';
const readdirp = dir=>
new Promise((pass,fail)=>
readdir(dir, (err, filenames) =>
err ? fail(err) : pass(mapResolve (dir) (filenames))));
const readfilep = path=>
new Promise((pass,fail)=>
readFile(path, 'utf8', (err,data)=>
err ? fail(err) : pass(data)));
const writefilep = path=> data=>
new Promise((pass,fail)=>
writeFile(path, data, err=>
err ? fail(err) : pass(path)));
In order to map functions to our Promises, we needed an fmap utility. Notice how we take care to bubble errors up.
Promise.prototype.fmap = function fmap(f) {
return new Promise((pass,fail) =>
this.then(x=> pass(f(x)), fail));
};
And here's the rest of the utilities
const fmap = f=> x=> x.fmap(f);
const mapResolve = dir=> map(x=>resolve(dir,x));
const map = f=> xs=> xs.map(x=> f(x));
const filter = f=> xs=> xs.filter(x=> f(x));
const match = re=> s=> re.test(s);
const concatp = xs=> Promise.all(xs);
const reduce = f=> y=> xs=> xs.reduce((y,x)=> f(y)(x), y);
const flatten = reduce(y=> x=> y.concat(Array.isArray(x) ? flatten (x) : x)) ([]);
Lastly, the one custom function that does your work
const createMap = map=> ({id, defaultMessage})=>
Object.assign(map, {[id]: defaultMessage});
And here's c.json
[
{
"id": "somethingelse",
"description": "something",
"defaultMessage": "Something!"
}
]
"Why so many little functions ?"
Well despite what you may think, you have a pretty big problem. And big problems are solved by combining several small solutions. The most prominent advantage of this code is that each function has a very distinct purpose and it will always produce the same results for the same inputs. This means each function can be used other places in your program. Another advantage is that smaller functions are easier to read, reason with, and debug.
Compare all of this to the other answers given here; #BlazeSahlen's in particular. That's over 60 lines of code that's basically only usable to solve this one particular problem. And it doesn't even filter out non-JSON files. So the next time you need to create a sequence of actions on reading/writing files, you'll have to rewrite most of those 60 lines each time. It creates lots of duplicated code and hard-to-find bugs because of exhausting boilerplate. And all that manual error-handling... wow, just kill me now. And he/she thought callback hell was bad ? haha, he/she just created yet another circle of hell all on his/her own.
All the code together...
Functions appear (roughly) in the order they are used
import {readdir, readFile, writeFile} from 'fs';
import {resolve} from 'path';
// logp: Promise<Value> -> Void
const logp = p=> p.then(x=> console.log(x), x=> console.err(x));
// fmap : Promise<a> -> (a->b) -> Promise<b>
Promise.prototype.fmap = function fmap(f) {
return new Promise((pass,fail) =>
this.then(x=> pass(f(x)), fail));
};
// fmap : (a->b) -> F<a> -> F<b>
const fmap = f=> x=> x.fmap(f);
// readdirp : String -> Promise<Array<String>>
const readdirp = dir=>
new Promise((pass,fail)=>
readdir(dir, (err, filenames) =>
err ? fail(err) : pass(mapResolve (dir) (filenames))));
// mapResolve : String -> Array<String> -> Array<String>
const mapResolve = dir=> map(x=>resolve(dir,x));
// map : (a->b) -> Array<a> -> Array<b>
const map = f=> xs=> xs.map(x=> f(x));
// filter : (Value -> Boolean) -> Array<Value> -> Array<Value>
const filter = f=> xs=> xs.filter(x=> f(x));
// match : RegExp -> String -> Boolean
const match = re=> s=> re.test(s);
// readfilep : String -> Promise<String>
const readfilep = path=>
new Promise((pass,fail)=>
readFile(path, 'utf8', (err,data)=>
err ? fail(err) : pass(data)));
// concatp : Array<Promise<Value>> -> Array<Value>
const concatp = xs=> Promise.all(xs);
// reduce : (b->a->b) -> b -> Array<a> -> b
const reduce = f=> y=> xs=> xs.reduce((y,x)=> f(y)(x), y);
// flatten : Array<Array<Value>> -> Array<Value>
const flatten = reduce(y=> x=> y.concat(Array.isArray(x) ? flatten (x) : x)) ([]);
// writefilep : String -> Value -> Promise<String>
const writefilep = path=> data=>
new Promise((pass,fail)=>
writeFile(path, data, err=>
err ? fail(err) : pass(path)));
// -----------------------------------------------------------------------------
// createMap : Object -> Object -> Object
const createMap = map=> ({id, defaultMessage})=>
Object.assign(map, {[id]: defaultMessage});
// do it !
readdirp('.')
.fmap(filter(match(/\.json$/)))
.fmap(map(readfilep))
.fmap(map(fmap(JSON.parse)))
.fmap(concatp)
.fmap(flatten)
.fmap(reduce(createMap)({}))
.fmap(data=> JSON.stringify(data, null, '\t'))
.fmap(writefilep(resolve(__dirname, 'result.json')))
.then(filename=> console.log('wrote results to %s', filename), err=>console.error(err));
Still having trouble following along?
It's not easy to see how these things work at first. This is a particularly squirrely problem because the data gets nested very quickly. Thankfully that doesn't mean our code has to be a big nested mess just to solve the problem ! Notice the code stays nice and flat even when we're dealing with things like a Promise of an Array of Promises of JSON...
// Here we are reading directory '.'
// We will get a Promise<Array<String>>
// Let's say the files are 'a.json', 'b.json', 'c.json', and 'run.js'
// Promise will look like this:
// Promise<['a.json', 'b.json', 'c.json', 'run.js']>
readdirp('.')
// Now we're going to strip out any non-JSON files
// Promise<['a.json', 'b.json', 'c.json']>
.fmap(filter(match(/\.json$/)))
// call `readfilep` on each of the files
// We will get <Promise<Array<Promise<JSON>>>>
// Don't freak out, it's not that bad!
// Promise<[Promise<JSON>, Promise<JSON>. Promise<JSON>]>
.fmap(map(readfilep))
// for each file's Promise, we want to parse the data as JSON
// JSON.parse returns an object, so the structure will be the same
// except JSON will be an object!
// Promise<[Promise<Object>, Promise<Object>, Promise<Object>]>
.fmap(map(fmap(JSON.parse)))
// Now we can start collapsing some of the structure
// `concatp` will convert Array<Promise<Value>> to Array<Value>
// We will get
// Promise<[Object, Object, Object]>
// Remember, we have 3 Objects; one for each parsed JSON file
.fmap(concatp)
// Your particular JSON structures are Arrays, which are also Objects
// so that means `concatp` will actually return Promise<[Array, Array, Array]
// but we'd like to flatten that
// that way each parsed JSON file gets mushed into a single data set
// after flatten, we will have
// Promise<Array<Object>>
.fmap(flatten)
// Here's where it all comes together
// now that we have a single Promise of an Array containing all of your objects ...
// We can simply reduce the array and create the mapping of key:values that you wish
// `createMap` is custom tailored for the mapping you need
// we initialize the `reduce` with an empty object, {}
// after it runs, we will have Promise<Object>
// where Object is your result
.fmap(reduce(createMap)({}))
// It's all downhill from here
// We currently have Promise<Object>
// but before we write that to a file, we need to convert it to JSON
// JSON.stringify(data, null, '\t') will pretty print the JSON using tab to indent
// After this, we will have Promise<JSON>
.fmap(data=> JSON.stringify(data, null, '\t'))
// Now that we have a JSON, we can easily write this to a file
// We'll use `writefilep` to write the result to `result.json` in the current working directory
// I wrote `writefilep` to pass the filename on success
// so when this finishes, we will have
// Promise<Path>
// You could have it return Promise<Void> like writeFile sends void to the callback. up to you.
.fmap(writefilep(resolve(__dirname, 'result.json')))
// the grand finale
// alert the user that everything is done (or if an error occurred)
// Remember `.then` is like a fork in the road:
// the code will go to the left function on success, and the right on failure
// Here, we're using a generic function to say we wrote the file out
// If a failure happens, we write that to console.error
.then(filename=> console.log('wrote results to %s', filename), err=>console.error(err));
All done !
Assumed files is list of arrays; [a, b, ...];
var res = {};
files.reduce((a, b) => a.concat(b), []).forEach(o => res[o.id] = o.defaultMessage);
But you need not to get all files at once.
Just add this code to onFileContent callback.
JSON.parse(fileContent).forEach(o => res[o.id] = o.defaultMessage);
Also, you should to add any final callback to your readFiles.
And in this callback:
fs.writeFile('result.json', JSON.stringify(res));
So, final solution for you:
var fs = require('fs');
function task(dir, it, cb) {
fs.readdir(dir, (err, names) => {
if (err) return cb([err]);
var errors = [], c = names.length;
names.forEach(name => {
fs.readFile(dir + name, 'utf-8', (err, data) => {
if (err) return errors.push(err);
try {
it(JSON.parse(data)); // We get a file data!
} catch(e) {
errors.push('Invalid json in ' + name + ': '+e.message);
}
if (!--c) cb(errors); // We are finish
});
});
});
}
var res = {};
task('C:/node/test/', (data) => data.forEach(o => res[o.id] = o.defaultMessage), (errors) => {
// Some files can be wrong
errors.forEach(err => console.error(err));
// But we anyway write received data
fs.writeFile('C:/node/test/result.json', JSON.stringify(res), (err) => {
if (err) console.error(err);
else console.log('Task finished. see results.json');
})
});
this should do it once you have your json in variables a and b:
var a = [
{
"id": "addEmoticon1",
"description": "Message to greet the user.",
"defaultMessage": "Hello, {name}!"
},
{
"id": "addPhoto1",
"description": "How are youu.",
"defaultMessage": "How are you??"
}
];
var b = [
{
"id": "close1",
"description": "Close it.",
"defaultMessage": "Close!"
}
];
var c = a.concat(b);
var res = []
for (var i = 0; i < c.length; i++){
res[ c[i].id ] = c[i].defaultMessage;
}
console.log(res);
Here's my solution:
function readFiles(dirname, onFileContent, onError) {
fs.readdir(dirname, function(err, filenames) {
/**
* We'll store the parsed JSON data in this array
* #type {Array}
*/
var fileContent = [];
if (err) {
onError(err);
} else {
filenames.forEach(function(filename) {
// Reading the file (synchronously) and storing the parsed JSON output (parsing from string to JSON object)
var jsonObject = JSON.parse(fs.readFileSync(dirname + filename, 'utf-8'));
// Pushing the parsed JSON output into array
fileContent.push(jsonObject);
});
// Calling the callback
onFileContent(fileContent);
}
});
}
readFiles('./files/',function(fileContent) {
/**
* We'll store the final output object here
* #type {Object}
*/
var output = {};
// Loop over the JSON objects
fileContent.forEach(function(each) {
// Looping within each object
for (var index in each) {
// Copying the `id` as key and the `defaultMessage` as value and storing in output object
output[each[index].id] = each[index].defaultMessage;
}
});
// Writing the file (synchronously) after converting the JSON object back to string
fs.writeFileSync('result.json', JSON.stringify(output));
}, function(err) {
throw err;
});
Notable difference is that I've not used the asynchronous readFile and writeFile functions as they'd needlessly complicate the example. This example is meant to showcase the use of JSON.parse and JSON.stringify to do what OP wants.
UPDATE:
var fs = require('fs');
function readFiles(dirname, onEachFilename, onComplete) {
fs.readdir(dirname, function(err, filenames) {
if (err) {
throw err;
} else {
// Prepending the dirname to each filename
filenames.forEach(function(each, index, array) {
array[index] = dirname + each;
});
// Calling aync.map which accepts these parameters:
// filenames <-------- array of filenames
// onEachFilename <--- function which will be applied on each filename
// onComplete <------- function to call when the all elements of filenames array have been processed
require('async').map(filenames, onEachFilename, onComplete);
}
});
}
readFiles('./files/', function(item, callback) {
// Read the file asynchronously
fs.readFile(item, function(err, data) {
if (err) {
callback(err);
} else {
callback(null, JSON.parse(data));
}
});
}, function(err, results) {
/**
* We'll store the final output object here
* #type {Object}
*/
var output = {};
if (err) {
throw err;
} else {
// Loop over the JSON objects
results.forEach(function(each) {
// Looping within each object
for (var index in each) {
// Copying the `id` as key and the `defaultMessage` as value and storing in output object
output[each[index].id] = each[index].defaultMessage;
}
});
// Writing the file (synchronously) after converting the JSON object back to string
fs.writeFileSync('result.json', JSON.stringify(output));
}
});
This is a simple asynchronous implementation of the same, using readFile. For more information, async.map.

Export CSV/XLS from Meteor application

What is the simplest way to export data to CSV from Meteor?
How to generate CSV?
What I've tried
Add Npm package:
$ meteor add meteorhacks:npm
Add Node.js CSV suite:
// packages.json
{
"csv": "0.4.0",
}
Add Iron Router package:
$ meteor add iron:router
Configure router on server:
// server/router.coffee
Router.map ->
#route 'exportCSV',
where: 'server'
path: '/export-csv/:id'
onAfterAction: ->
data = ... // Generated CSV
filename = 'filename.csv'
headers =
'Content-type': 'text/csv'
'Content-Disposition': 'attachment; filename=' + filename
#response.writeHead 200, headers
#response.end file
For my use-case, all of the data was already published to the client. So I decided to generate the file there, using FileSaver.
Here's a basic class to build a csv by calling addRow(), then call download('xxx.csv') to have the user download the file.
class CsvBuilder
constructor: ->
#content = []
line: ->
row = [].slice.call(arguments)
if row.length == 0
#addBlankRow()
else
#addRow(row)
return
addRow: (row)->
#content.push(#row2Csv(row), "\n")
return
addBlankRow: ->
#content.push("", "\n")
return
row2Csv: (row)->
d = ''
for cell in row
d += '"' + (cell + "").replace(/"/g, '""') + '",'
return d
download: (filename)->
try
isFileSaverSupported = !!new FileSaver.Blob()
unless isFileSaverSupported
window.alert("Save as CSV not supported");
return
contentBlob = new FileSaver.Blob(#content, {type: "text/csv;charset=utf-8"})
FileSaver.saveAs(contentBlob, filename)
return
destroy: ->
#content = []
Using the FileSave.js from eligrey/Filesaver and save in lib folder in you client
'click .btnRawCSV' : function() {
var rawData = Gifts.find({
receiptdate:{
$gte: Session.get("giftsStartDate"),
$lte: Session.get("giftsEndDate")
}
}).fetch();
csv = json2csv( rawData, true, true );
var blob = new Blob([csv], {type: "text/plain;charset=utf-8;",});
saveAs(blob, "rawgifts.csv");
},