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