yea-stringify
Version:
Yet Another JSON.stringify with some additional features.
220 lines (213 loc) • 7.85 kB
JavaScript
(function() {
"use strict";
var strRepeat = function(s, n) {
var S = "";
while(n-- > 0) {
S += s;
}
return S;
};
var searchRecursiveRef = function(obj, parents, names) {
for(var i = 0; i < parents.length; i++) {
if(obj === parents[i]) {
return ["$(ROOT)"].concat(names.slice(1, i)).join(".");
}
}
return null;
};
var _stringify = function(obj, replacer, space, opts, name, level, parents, names) {
level = level || 0;
parents = parents || [];
names = names || [];
var qname = "";
var getRecursiveOpt = function(recursive, opts) {
var opt = {
"recursive": recursive,
"eliminate": opts["eliminate-undefined"]
};
if(opts["eliminate-recursive"] && opt.recursive) {
opt.eliminate = true;
}
return opt;
};
if(name == null && replacer && typeof(replacer) == 'function') {
var opt = getRecursiveOpt(false, opts);
// This replacer call seems like to cause context problem.
// Auto boxing?
obj = replacer.call(obj, "", obj, opt);
if(typeof(obj) === "undefined") {
return;
}
}
if(name == null) {
qname = "";
} else if(typeof(name) == 'number') {
if(opts["show-index"]) {
qname = name = "/* [" + name + "] */ ";
} else {
qname = name = "";
}
} else {
qname = JSON.stringify(name) + ":";
}
var indent = "";
if(space != null) {
indent = strRepeat(space, level);
qname = indent + qname;
}
if(obj == null) {
return qname + "null";
}
var originalPath = searchRecursiveRef(obj, parents, names);
if(originalPath != null) {
if(opts["show-recursive-reference"]) {
return qname + "{ /* RECURSIVE REFERENCE to " + originalPath + " */ }";
}
return qname + "{}";
} else if(typeof(obj) == 'function') {
if(opts["show-function"]) {
return qname + 'function(){}';
}
return null;
} else if(Array.isArray(obj)) {
parents.push(obj);
names.push(name);
var lines = [];
obj.forEach(function(value, i) {
if(typeof(value) == "function") {
if(opts["show-function"]) {
value = "function(){}";
} else {
value = null;
}
}
var recursive = false;
if(typeof(value) == 'object') {
recursive = (searchRecursiveRef(value, parents, names) != null);
}
var eliminate = false;
var opt = getRecursiveOpt(recursive, opts);
if(replacer) {
if(typeof(replacer) == 'function') {
value = replacer.call(obj, i, value, opt);
}
}
eliminate = opt.eliminate;
if(!eliminate) {
lines.push(_stringify(value, replacer, space, opts,
i, level + 1, parents, names));
}
});
parents.pop();
names.pop();
var lengthDesc = "";
if(opts["show-length"]) {
lengthDesc = " /* " + obj.length + " elements */ ";
}
if(lines.length == 0) {
return qname + "[" + lengthDesc + "]";
}
if(space == null) {
return qname + "[" + lengthDesc + lines.join(",") + "]";
}
return qname + "[" + lengthDesc + "\n"
+ lines.join(",\n") + "\n" + indent + "]";
} else if(typeof(obj) == 'object') {
parents.push(obj);
names.push(name);
var lines = [];
var keys = Object.keys(obj);
if(opts["sort-keys"]) {
keys = keys.sort();
}
keys.forEach(function(key) {
var value = obj[key];
if(typeof(value) == "function") {
if(!opts["show-function"]) {
return;
}
}
var recursive = false;
if(typeof(value) == 'object') {
recursive = (searchRecursiveRef(value, parents, names) != null);
}
var eliminate = false;
var opt = getRecursiveOpt(recursive, opts);
if(replacer) {
if(typeof(replacer) == 'function') {
value = replacer.call(obj, key, value, opt);
} else if(replacer.indexOf(key) < 0) {
value = undefined;
}
}
eliminate = opt.eliminate;
if(!eliminate) {
lines.push(_stringify(value, replacer, space, opts,
key, level + 1, parents, names));
}
});
parents.pop();
names.pop();
var lengthDesc = "";
if(opts["show-length"]) {
lengthDesc = " /* " + keys.length + " keys */ ";
}
if(lines.length == 0) {
return qname + "{" + lengthDesc + "}";
}
if(space == null) {
return qname + "{" + lengthDesc + lines.join(",") + "}";
}
return qname + "{" + lengthDesc + "\n"
+ lines.join(",\n") + "\n" + indent + "}";
} else {
return qname + JSON.stringify(obj);
}
};
var defaultOpts = function() {
return {
"eliminate-undefined": false,
"eliminate-recursive": false,
"show-recursive-reference": true,
"show-function": false,
"show-index" : false,
"show-length" : false,
"sort-keys" : false
};
};
var mixinOpts = function(dst,src) {
Object.keys(src).forEach(function(key) {
if(key in dst) {
dst[key] = src[key];
} else {
throw "Error: unknown option " + key + " is specified.";
}
});
};
var moduleOpts = defaultOpts();
var stringify = function(obj, replacer, space, opts) {
var localOpts = defaultOpts();
opts = opts || {};
mixinOpts(localOpts, moduleOpts);
mixinOpts(localOpts, opts);
if(replacer != null) {
if(typeof(replacer) != "function" && !Array.isArray(replacer)) {
throw "Error: the type of replacer must be function or array.";
}
}
if(space != null) {
if(typeof(space) == "number") {
space = strRepeat(" ", space);
} else if(typeof(space) != "string") {
throw "Error: the type of space must be number or string.";
}
}
return _stringify(obj, replacer, space, localOpts);
};
stringify.setOption = function(opts) {
opts = opts || {};
mixinOpts(moduleOpts, opts);
return module.exports;
};
module.exports = stringify;
}());