节点JS:从树json制作平面json [英] Node JS: Make a flat json from a tree json

查看:72
本文介绍了节点JS:从树json制作平面json的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在编写一个node.js脚本,将所有json文件合并到一个目录中,并将结果存储为新的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!"
  }
]

我最后需要的是:

result.json

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

我写了一个node.js脚本:

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

它会提取"id"和"defaultMessage",但无法正确附加.

It extracts the 'id' and 'defaultMessage' but is not able to append correctly.

我得到的东西:

result.json

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

每次我运行脚本时,此输出都是不同的.

This output is different every time I run my script.

  • 目标1:用双引号引起来,

  • Aim 1: Surround items in double quotes,

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

Aim 2: Add curly braces at the top and at the end

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

Aim 3: No comma at the end of last element

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

Aim 4: Same output every time I run my script

推荐答案

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

此答案的结尾有一个很大的解释.首先,让我们尝试从大角度考虑.

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

控制台输出

wrote results to /path/to/result.json

result.json (我添加了c.json并提供了一些数据,表明该文件适用于2个以上的文件)

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


实施

我为readdirreadFilewriteFile

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实用程序.请注意,我们会谨慎处理错误.

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

这是其余的实用程序

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

这是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.

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

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.

所有代码在一起...

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

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


仍然遇到麻烦吗?

乍一看这些东西是如何工作的并不容易.这是一个特别棘手的问题,因为数据非常快速地嵌套.值得庆幸的是,这并不意味着我们的代码必须为了解决该问题而陷入困境!请注意,即使当我们处理诸如JSON承诺数组之类的事情时,代码仍保持良好的状态.

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

全部完成!

这篇关于节点JS:从树json制作平面json的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆