UPDATE:经公子大大提醒,使用JSON API就可以做到下面的效果。
请输入图片描述
(这后面的内容不用看了)
最近在使用Node.js开发项目,由于JavaScript内置了对JSON的支持,自然而然想到了使用JSON编写配置文件。

// app.json
{"mode": "prod", "log_path": "/data"}; //还有更多内容
var app = require('./app.json');
var fs = require('fs');
// 某些操作,保存app
fs.writeFile('/path/to/app.json', JSON.stringify(app),
    function(err) {
        if(err) throw err;
    });

如果配置文件比较复杂时,就需要分多行添加缩进等重新排版,但是只要程序读写保存过一次后,原来辛辛苦苦排版好的JSON文件内容又扎堆在一起了。对于开发人员来说或许不那么头疼,但对于运维人员(我司运维工程师们)却不是这样的,为了方便他们及我们读写修改JSON配置文件,于是就在网上搜索类json pretty tools的Node.js模块,便找到了prettyjson这个模块,不过这个模块时将对象输出成YAML风格,不太符合我的需求。
省略中间的过程。
修改后的代码:

'use strict';

// ### Render function
// *Parameters:*
//
// * **`data`**: Data to render
// * **`options`**: Hash with different options to configure the parser
// * **`indentation`**: Base indentation of the parsed output
//
// *Example of options hash:*
//
//     {
//       defaultIndentation: 2     // Indentation on nested objects
//     }
exports.render = function render(data, options, indentation) {
  // Default values
  indentation = indentation || 0;
  options = options || {};
  options.defaultIndentation = options.defaultIndentation || 2;

  var output = [];

  // Helper function to detect if an object can be directly serializable
  var isSerializable = function(input, onlyPrimitives) {
    if (typeof input === 'boolean' ||
        typeof input === 'number' || input === null) {
      return true;
    }
    if (typeof input === 'string' && input.indexOf('\n') === -1) {
      return true;
    }

    return false;
  };

  var indentLines = function(string, spaces){
    var lines = string.split('\n');
    lines = lines.map(function(line){
      return indent(spaces) + line;
    });
    return lines.join('\n');
  };

  var outputData = function(input) {

    if (typeof input === 'string') {
      // Print strings wraped by double quote
      return '"' + input + '"';
    }

    if (input === true) {
      return 'true';
    }
    if (input === false) {
      return 'false';
    }
    if (input === null) {
      return '';
    }
    if (typeof input === 'number') {
      return input;
    }

    return input;
  };
  var removeLastComma = function(output) {
      var lastElement = output[output.length-1];
      output[output.length-1] = lastElement.substr(0, lastElement.length-1);
  };
  var indent = function(numSpaces) {
    return new Array(numSpaces+1).join(' ');
  };
  // Render a string exactly equal
  if (isSerializable(data)) {
    output.push(indent(indentation) + outputData(data));
  }
  else if (typeof data === 'string') {
    var lines = data.split('\n');
    lines.map(function(line){
      return indent(indentation + options.defaultIndentation) + '"' + line + '"';
    });
    output.push(lines.join(',\n'));
  }
  else if (Array.isArray(data)) {
    var line = indent(indentation);
    indentation = indentation + options.defaultIndentation;
    output.push(line + '[');
    // If the array is empty
    if (data.length === 0) {
      output.push(indent(indentation) +' ');
    } else {
      data.forEach(function(element) {
        if(isSerializable(element)) {
            output.push(indent(indentation) + outputData(element) + ',');
        }else {
            output.push(exports.render(element, options, indentation) + ',');
        }
      });
      removeLastComma(output);
    }
    output.push(line + '],');
  }
  else if (typeof data === 'object') {
    var line = indent(indentation);
    output.push(line+'{');
    var key;
    var isError = data instanceof Error;
    indentation = indentation + options.defaultIndentation;
    Object.getOwnPropertyNames(data).forEach(function(i) {
      // Prepend the index at the beginning of the line
      key = ('"' + i +'"'+ ': ');
      key = indent(indentation) + key;

      // Skip `undefined`, it's not a valid JSON value.
      if (data[i] === undefined) {
        return;
      }
      if(isSerializable(data[i])) {
        output.push(key + outputData(data[i]) + ',');
      }else {
        var temp = exports.render(data[i], options, indentation);
        output.push(key + temp.trim() + ',');
      }
    });
    removeLastComma(output);
    output.push(line + '},');
  }
  removeLastComma(output);
  // Return all the lines as a string
  return output.join('\n');
};

// ### Render from string function
// *Parameters:*
//
// * **`data`**: Data to render as a string
// * **`options`**: Hash with different options to configure the parser
// * **`indentation`**: Base indentation of the parsed output
//
// *Example of options hash:*
//
//     {
//       defaultIndentation: 2     // Indentation on nested objects
//     }
exports.renderString = function renderString(data, options, indentation) {

  var output = '';
  var parsedData;
  // If the input is not a string or if it's empty, just return an empty string
  if (typeof data !== 'string' || data === '') {
    return '';
  }

  // Remove non-JSON characters from the beginning string
  if (data[0] !== '{' && data[0] !== '[') {
    var beginingOfJson;
    if (data.indexOf('{') === -1) {
      beginingOfJson = data.indexOf('[');
    } else if (data.indexOf('[') === -1) {
      beginingOfJson = data.indexOf('{');
    } else if (data.indexOf('{') < data.indexOf('[')) {
      beginingOfJson = data.indexOf('{');
    } else {
      beginingOfJson = data.indexOf('[');
    }
    output += data.substr(0, beginingOfJson) + '\n';
    data = data.substr(beginingOfJson);
  }

  try {
    parsedData = JSON.parse(data);
  } catch (e) {
    // Return an error in case of an invalid JSON
    return 'Error:' + ' Not valid JSON!';
  }

  // Call the real render() method
  output += exports.render(parsedData, options, indentation);
  return output;
};

这样之后原来程序代码基本不变,在保存对象到JSON文件时做了调整:

+var prettyjson = require('./lib/prettyjson');
-fs.writeFile('/path/to/app.json', JSON.stringify(app),
+fs.writeFile('/path/to/app.json', prettyjson.render(app),
    function(err) {
        if(err) throw err;
    });

Honwhy
7k 声望96 粉丝

神兽党有福了