Nodo JS: Haz un json plano de un árbol json.

Estaba escribiendo un script node.js para combinar todos los archivos json en un directorio y almacenar el resultado como un nuevo archivo json. Intenté hacer el trabajo en gran medida pero tiene pocos defectos.

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

Lo que finalmente necesito es:

result.json

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

Escribí un script 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; }); 

Extrae el ‘id’ y ‘defaultMessage’ pero no puede adjuntar correctamente.

Lo que consigo:

result.json

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

Esta salida es diferente cada vez que ejecuto mi script.

  • Objetivo 1: rodear elementos entre comillas dobles,

  • Objetivo 2: añadir llaves en la parte superior y al final

  • Objetivo 3: No hay coma al final del último elemento

  • Objetivo 4: la misma salida cada vez que ejecuto mi script

Aquí está mi solución:

 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 diferencia es que no he usado las readFile asíncronas readFile y writeFile , ya que complican innecesariamente el ejemplo. Este ejemplo pretende mostrar el uso de JSON.parse y JSON.stringify para hacer lo que OP quiere.


ACTUALIZAR:

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

Esta es una implementación asíncrona simple de la misma, utilizando readFile . Para más información, async.map .

Comenzaré con la solución terminada …

Hay una gran explicación al final de esta respuesta. Vamos a tratar de pensar en grande para un poquito por primera vez.

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

Salida de consola

 wrote results to /path/to/result.json 

result.json (agregué un c.json con algunos datos para mostrar que esto funciona con más de 2 archivos)

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

Implementación

Hice interfaces basadas en Promise para readdir y readFile y 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))); 

Para poder asignar funciones a nuestras promesas, necesitábamos una utilidad fmap . Fíjate en cómo nos encargamos de burbujear errores.

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

Y aquí está el rest de las utilidades.

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

Por último, la única función personalizada que hace tu trabajo.

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

Y aqui esta c.json

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

“¿Por qué tantas funciones pequeñas?”

Bueno, a pesar de lo que pienses, tienes un problema bastante grande. Y los grandes problemas se resuelven combinando varias soluciones pequeñas. La ventaja más destacada de este código es que cada función tiene un propósito muy distinto y siempre producirá los mismos resultados para las mismas entradas. Esto significa que cada función puede ser utilizada en otros lugares en su progtwig. Otra ventaja es que las funciones más pequeñas son más fáciles de leer, razonar y depurar.

Compara todo esto con las otras respuestas dadas aquí; @ BlazeSahlen en particular. Eso es más de 60 líneas de código que, básicamente, solo se pueden utilizar para resolver este problema en particular. Y ni siquiera filtra archivos que no son JSON. Así que la próxima vez que necesite crear una secuencia de acciones en la lectura / escritura de archivos, tendrá que volver a escribir la mayoría de esas 60 líneas cada vez. Crea un montón de códigos duplicados y errores difíciles de encontrar debido al exhaustivo repetitivo. Y todo ese manejo manual de errores … guau, solo mátame ahora. ¿Y pensó que el infierno de callback era malo? jaja, él / ella acaba de crear otro círculo del infierno por su cuenta.


Todo el código junto …

Las funciones aparecen (aproximadamente) en el orden en que se utilizan.

 import {readdir, readFile, writeFile} from 'fs'; import {resolve} from 'path'; // logp: Promise -> Void const logp = p=> p.then(x=> console.log(x), x=> console.err(x)); // fmap : Promise -> (a->b) -> Promise Promise.prototype.fmap = function fmap(f) { return new Promise((pass,fail) => this.then(x=> pass(f(x)), fail)); }; // fmap : (a->b) -> F -> F const fmap = f=> x=> x.fmap(f); // readdirp : String -> Promise> const readdirp = dir=> new Promise((pass,fail)=> readdir(dir, (err, filenames) => err ? fail(err) : pass(mapResolve (dir) (filenames)))); // mapResolve : String -> Array -> Array const mapResolve = dir=> map(x=>resolve(dir,x)); // map : (a->b) -> Array -> Array const map = f=> xs=> xs.map(x=> f(x)); // filter : (Value -> Boolean) -> Array -> Array const filter = f=> xs=> xs.filter(x=> f(x)); // match : RegExp -> String -> Boolean const match = re=> s=> re.test(s); // readfilep : String -> Promise const readfilep = path=> new Promise((pass,fail)=> readFile(path, 'utf8', (err,data)=> err ? fail(err) : pass(data))); // concatp : Array> -> Array const concatp = xs=> Promise.all(xs); // reduce : (b->a->b) -> b -> Array -> b const reduce = f=> y=> xs=> xs.reduce((y,x)=> f(y)(x), y); // flatten : Array> -> Array const flatten = reduce(y=> x=> y.concat(Array.isArray(x) ? flatten (x) : x)) ([]); // writefilep : String -> Value -> Promise 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)); 

¿Sigues teniendo problemas para seguirlo?

No es fácil ver cómo funcionan estas cosas al principio. Este es un problema particularmente grave porque los datos se anidan muy rápidamente. ¡Afortunadamente, eso no significa que nuestro código tenga que ser un gran desastre para resolver el problema! Tenga en cuenta que el código sigue siendo agradable, incluso cuando estamos tratando con cosas como una promesa de una matriz de promesas de JSON …

 // Here we are reading directory '.' // We will get a Promise> // 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 >>> // Don't freak out, it's not that bad! // Promise<[Promise, Promise. Promise]> .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, Promise, Promise]> .fmap(map(fmap(JSON.parse))) // Now we can start collapsing some of the structure // `concatp` will convert Array> to Array // 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> .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 // where Object is your result .fmap(reduce(createMap)({})) // It's all downhill from here // We currently have Promise // 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 .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 // You could have it return Promise 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)); 

Todo listo !

Los archivos asumidos son una lista de matrices; [a, b, …];

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

Pero no necesitas obtener todos los archivos a la vez.
Simplemente agregue este código a la onFileContent llamada onFileContent .

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

Además, debes agregar cualquier callback final a tus readFiles .
Y en este callback:

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

Entonces, la solución final para usted:

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

esto debería hacerlo una vez que tengas tu json en las variables ayb:

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