Node JS:从树json中创build一个平坦的json

我正在编写一个node.js脚本来合并目录中的所有json文件,并将结果存储为一个新的json文件。 我试图在很大程度上做这项工作,但没有什么缺点。

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!" } ] 

我最终需要的是:

result.json

 { "addEmoticon1": "Hello, {name}!", "addPhoto1": "How are you??", "close1": "Close!" } 

我写了一个node.js脚本:

 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; }); 

它提取'id'和'defaultMessage',但不能正确附加。

我得到:

result.json

 addEmoticon1: addPhoto1: Hello, {name}!, close1: How are you??, Close!, 

每次我运行我的脚本时,这个输出是不同的。

  • 目标1:用双引号环绕项目,

  • 目标2:在顶部和底部添加大括号

  • 目标3:在最后一个元素末尾没有逗号

  • 目标4:每次运行脚本时输出相同

这是我的解决scheme:

 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; }); 

值得注意的区别是我没有使用asynchronousreadFilewriteFile函数,因为它们会不必要地使示例复杂化。 这个例子是为了展示使用JSON.parseJSON.stringify来做OP所需要的。


更新:

 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)); } }); 

这是一个简单的asynchronous实现,使用readFile 。 有关更多信息, async.map 。

我将从完成的解决scheme开始

这个答案的最后有一个很大的解释。 让我们先尝试一下思路。

 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)); 

控制台输出

 wrote results to /path/to/result.json 

c.json (我用一些数据添加了一个c.json来显示这个工作超过2个文件)

 { "addEmoticon1": "Hello, {name}!", "addPhoto1": "How are you??", "close1": "Close!", "somethingelse": "Something!" } 

履行

我为readdirreadFilewriteFile做了基于Promise的接口

 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))); 

为了将函数映射到我们的Promises,我们需要一个fmap实用程序。 请注意我们如何注意将错误提示出来。

 Promise.prototype.fmap = function fmap(f) { return new Promise((pass,fail) => this.then(x=> pass(f(x)), fail)); }; 

这里是其余的工具

 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)) ([]); 

最后,一个自定义的function,你的工作

 const createMap = map=> ({id, defaultMessage})=> Object.assign(map, {[id]: defaultMessage}); 

这里是c.json

 [ { "id": "somethingelse", "description": "something", "defaultMessage": "Something!" } ] 

“为什么这么多小function?”

不pipe你怎么想,你都有一个很大的问题。 结合几个小的解决scheme解决了大问题。 这个代码最突出的优点是每个函数都有一个非常明确的目的,并且对于相同的input,它总是会产生相同的结果。 这意味着每个函数都可以在程序中的其他地方使用。 另一个优点是较小的函数更容易阅读,推理和debugging。

把这一切和这里给出的其他答案进行比较。 特别是BlazeSahlen。 这是超过60行代码,基本上只能用于解决这个特定的问题。 它甚至不会过滤出非JSON文件。 所以下一次你需要在读/写文件时创build一系列的动作,你必须每次重写大部分60行。 它会造成大量重复的代码和难以发现的错误,因为用尽了样板。 而所有那些手动的error handling…哇,现在就杀了我。 他/她认为callback地狱是不好的? 哈哈,他/她只是自己创造了又一圈地狱。


所有的代码在一起…

函数按其使用顺序出现(大致)

 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)); 

还有问题跟着下?

首先看到这些东西是如何工作的并不容易。 这是一个特别棘手的问题,因为数据嵌套得非常快。 谢天谢地,这并不意味着我们的代码只是为了解决问题而成为一个大的嵌套混乱。 注意,即使我们正在处理类似JSON数组承诺的Promise时,代码仍然保持良好和平坦…

 // 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)); 

全做完了 !

假定的文件是数组列表; [a,b,…];

 var res = {}; files.reduce((a, b) => a.concat(b), []).forEach(o => res[o.id] = o.defaultMessage); 

但是你不需要一次获得所有文件。
只需将此代码添加到onFileContentcallback。

 JSON.parse(fileContent).forEach(o => res[o.id] = o.defaultMessage); 

此外,你应该添加任何最后的callback你的readFiles
在这个callback中:

 fs.writeFile('result.json', JSON.stringify(res)); 

那么,最后的解决办法是:

 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'); }) }); 

这应该这样做,一旦你有你的JSONvariablesA和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);