Node JS:从树json创建一个平面json

时间:2016-05-19 06:39:41

标签: json node.js

我正在编写一个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:每次运行脚本时输出相同

4 个答案:

答案 0 :(得分:5)

我将从完成的解决方案开始......

这个答案的结尾有一个很大的解释。让我们先尝试一下大局。

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

result.json (我添加了c.json一些数据,以证明这适用于2个以上的文件)

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

<强>实施

我为Promisereaddir以及readFile

制作了基于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)));

为了将函数映射到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)) ([]);

最后,一个用于完成工作的自定义功能

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

这里c.json

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

&#34;为什么这么多小功能?&#34;

尽管你可能会想到,但你有一个很大的问题。通过结合几个小解决方案解决了大问题。该代码最突出的优点是每个函数都有一个非常独特的目的,它将始终为相同的输入产生相同的结果。这意味着每个函数都可以在程序的其他位置使用。另一个优点是较小的功能更易于阅读,推理和调试。

将所有这些与此处给出的其他答案进行比较; @ BlazeSahlen尤其如此。超过60行代码基本上只能用来解决这个特定问题。它甚至不会过滤掉非JSON文件。因此,下次您需要在读/写文件时创建一系列操作时,您每次都必须重写这60行中的大部分。由于耗尽了样板,它会产生大量重复的代码和难以发现的错误。所有那些手动错误处理......哇,现在就杀了我。 他/她认为回调地狱很糟糕?哈哈,他/她刚刚创造了另一个地狱圈。

所有代码一起......

功能按照使用顺序(大致)显示

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的承诺数组的承诺这样的事情,代码也会保持良好和平坦......

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

全部完成!

答案 1 :(得分:1)

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

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

但你不需要立刻获得所有文件 只需将此代码添加到onFileContent回调。

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

此外,您应该向readFiles添加任何最终回调 在这个回调中:

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

答案 2 :(得分:0)

这应该在变量a和b中包含json后执行:

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

答案 3 :(得分:0)

这是我的解决方案:

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

值得注意的是,我使用了异步readFilewriteFile函数,因为它们不必要地使示例复杂化。此示例旨在展示使用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));
    }
});

这是使用readFile的简单异步实现。有关详细信息,请async.map