format-to-json
Version:
Format string to a json like template.
687 lines (645 loc) • 19.9 kB
JavaScript
function (root) {
const BREAK = '\r\n';
const SPACE = ' ';
const OPTIONS = {
indent: 2,
isExpand: true,
isStrict: false,
isEscape: false,
isUnscape: false,
keyQtMark: '"', // '\'' | '\"' | '';
valQtMark: '"', // '\'' | '\"';
};
const ESCAPES_MAP = [
{ ptn: /\r\n/gm, str: '' },
{ ptn: /\n\r/gm, str: '' },
{ ptn: /\n/gm, str: '\\n' },
{ ptn: /\r/gm, str: '\\r' },
{ ptn: //gm, str: '\\b' },
{ ptn: //gm, str: '\\v' },
{ ptn: /\f/gm, str: '\\f' },
{ ptn: /\t/gm, str: '\\t' },
];
const MESSAGES_MAP = {
err: () => `Parse Error, an excessive abnormal Json!`,
war: (rowIdx) => `Formated ${rowIdx} lines, abnormal JSON source!`,
scc: (rowIdx) => `Success formated ${rowIdx} lines!`,
val: (rowIdx) => `Invalid value in line: ${rowIdx}`,
ost: (rowIdx) => `Expect a string in line: ${rowIdx}`,
col: (rowIdx) => `Expect a colon in line: ${rowIdx}`,
end: (rowIdx, brc) => `Expect a comma or a \"${brc}\" in line: ${rowIdx}`,
};
/**
* =================================================================
* The main function of `format-to-json` util.
* @param { string } source
* @param { Object } options
* =================================================================
*/
function fmt2json(source, options) {
/**
* The variables.
* Should be initialized at the beginning of the format.
* { fmtSign } Possibal value: 'ost' | 'col' | 'val' | 'end' | 'war' | 'scc' | 'err';
* { fmtType } Possibal value: 'info' | 'success' | 'warning' | 'danger';
*/
let fmtSource = source,
curLevel = 0,
curIndex = 1,
exceptType = '',
exceptSign = '',
signsQueue = '',
baseIndent = '',
isSrcValid = true,
isFmtError = false,
withDetails = false,
fmtResult = '',
fmtType = 'info',
fmtSign = '',
fmtLines = 0,
message = '',
errFormat = false,
errNear = '',
errIndex = NaN,
errExpect = '';
const fmtOptions = Object.assign({}, OPTIONS);
if (options) {
if (typeof options.withDetails === 'boolean') {
withDetails = options.withDetails;
}
if (typeof options.expand === 'boolean') {
fmtOptions.isExpand = options.expand;
}
if (typeof options.strict === 'boolean') {
fmtOptions.isStrict = options.strict;
}
if (typeof options.escape === 'boolean') {
fmtOptions.isEscape = options.escape;
}
if (typeof options.unscape === 'boolean') {
fmtOptions.isUnscape = options.unscape;
}
if (typeof options.indent === 'number' && options.indent > 0) {
fmtOptions.indent = options.indent;
}
if (["'", '"', ''].indexOf(options.keyQtMark) > -1) {
fmtOptions.keyQtMark = options.keyQtMark;
}
if (["'", '"'].indexOf(options.valQtMark) > -1) {
fmtOptions.valQtMark = options.valQtMark;
}
}
baseIndent = getBaseIndent();
try {
try {
if (fmtSource !== '') eval(`fmtSource = ${fmtSource}`);
if (fmtSource === '' || ['object', 'boolean'].indexOf(typeof fmtSource) > -1) {
doNormalFormat(fmtSource);
} else {
if (fmtOptions.isUnscape) {
fmtSource = fmtSource.replace(/\\"/gm, '"').replace(/\\\\/gm, '\\');
}
doSpecialFormat();
}
} catch (err) {
// console.log(err);
if (fmtOptions.isUnscape) {
fmtSource = fmtSource.replace(/\\"/gm, '"').replace(/\\\\/gm, '\\');
}
doSpecialFormat();
}
} catch (err) {
// console.log(err);
isFmtError = true;
} finally {
setFmtStatus();
return withDetails
? {
result: fmtResult,
fmtType,
fmtSign,
fmtLines,
message,
errFormat,
errIndex,
errNear,
errExpect,
}
: fmtResult;
}
/**
* =================================================================
* Format the Normal JSON source
* @param {*} src
* =================================================================
*/
function doNormalFormat(src) {
if ([true, false, null, ''].indexOf(src) > -1) {
return (fmtResult += String(src));
}
if (fmtOptions.isStrict) {
src = JSON.parse(JSON.stringify(src));
}
src instanceof Array ? arrayHandler(src) : objectHandler(src);
}
function arrayHandler(srcArr) {
let curIndent;
if (srcArr.length > 0) {
fmtResult += brkLine4Normal('[');
curLevel++;
for (let i = 0; i < srcArr.length; i++) {
curIndent = fmtOptions.isExpand ? getCurIndent() : '';
fmtResult += curIndent;
valueHandler(srcArr[i]);
fmtResult += brkLine4Normal(i < srcArr.length - 1 ? ',' : '');
}
curLevel--;
curIndent = fmtOptions.isExpand ? getCurIndent() : '';
fmtResult += curIndent + ']';
} else {
fmtResult += '[]';
}
}
function objectHandler(srcObj) {
const objKeys = Object.keys(srcObj);
if (objKeys.length > 0) {
let curIndent,
index = 0;
fmtResult += brkLine4Normal('{');
curLevel++;
for (const key in srcObj) {
index++;
const prop = quoteNormalStr(key, fmtOptions.keyQtMark);
curIndent = fmtOptions.isExpand ? getCurIndent() : '';
fmtResult += curIndent;
fmtResult += prop;
fmtResult += fmtOptions.isExpand ? ': ' : ':';
valueHandler(srcObj[key]);
fmtResult += brkLine4Normal(index < objKeys.length ? ',' : '');
}
curLevel--;
curIndent = fmtOptions.isExpand ? getCurIndent() : '';
fmtResult += curIndent + '}';
} else {
fmtResult += '{}';
}
}
function valueHandler(value) {
switch (typeof value) {
case 'undefined':
case 'function':
return (fmtResult += String(value));
case 'number':
case 'boolean':
return (fmtResult += value);
case 'object':
return doNormalFormat(value);
case 'string':
return (fmtResult += quoteNormalStr(value, fmtOptions.valQtMark));
}
}
/**
* =================================================================
* Format the Abnormal JSON source
* =================================================================
*/
function doSpecialFormat() {
fmtSource = fmtSource.replace(/^\s*/, '');
if (fmtSource.length === 0) return;
let isMatched = false;
switch (fmtSource[0]) {
case "'":
case '"':
isMatched = true;
quotaHandler();
break;
case ':':
isMatched = true;
colonHandler();
break;
case ',':
isMatched = true;
commaHandler();
break;
case '{':
isMatched = true;
objPreHandler();
break;
case '}':
isMatched = true;
objEndHandler();
break;
case '[':
isMatched = true;
arrPreHandler();
break;
case ']':
isMatched = true;
arrEndHandler();
break;
case '(':
isMatched = true;
tupPreHandler();
break;
case ')':
isMatched = true;
tupEndHandler();
break;
}
if (!isMatched) {
const unicMt = fmtSource.match(/^u(\s)?'|^u(\s)?"/);
if (unicMt) {
isMatched = true;
unicHandler(unicMt[0]);
}
}
if (!isMatched) {
const numbMt = fmtSource.match(/^(-?[0-9]+\.?[0-9]*|0[xX][0-9a-fA-F]+)/);
if (numbMt) {
isMatched = true;
numbHandler(numbMt[0]);
}
}
if (!isMatched) {
const boolMt = fmtSource.match(/^(true|false|True|False)/);
if (boolMt) {
isMatched = true;
boolHandler(boolMt[0]);
}
}
if (!isMatched) {
const nullMt = fmtSource.match(/^(null|undefined|None|NaN)/);
if (nullMt) {
isMatched = true;
nullHandler(nullMt[0]);
}
}
if (!isMatched) otheHandler();
return doSpecialFormat();
}
function quotaHandler() {
const rest = getSrcRest();
const restIdx = getNextQuotaIndex(fmtSource[0], rest);
chkFmtExpect(fmtSource[0]);
const quoteMt = fmtSource.substr(0, 1);
const isProperty = exceptType === 'ost';
let strInQuote = '';
if (restIdx > -1) {
strInQuote = fmtSource.substr(1, restIdx);
fmtResult += quoteSpecialStr(strInQuote, quoteMt, isProperty);
setFmtExpect(fmtSource[0]);
fmtSource = getSrcRest(restIdx + 2);
} else {
strInQuote = fmtSource.substr(1);
fmtResult += quoteSpecialStr(strInQuote, quoteMt, isProperty);
setFmtExpect('!');
fmtSource = '';
}
}
function colonHandler() {
fmtResult += fmtOptions.isExpand ? ': ' : ':';
chkFmtExpect(fmtSource[0]);
setFmtExpect(fmtSource[0]);
fmtSource = getSrcRest();
}
function commaHandler() {
const curIndent = getCurIndent();
if (fmtOptions.isExpand) curIndex++;
fmtResult += fmtOptions.isExpand ? `,${BREAK + curIndent}` : ',';
chkFmtExpect(fmtSource[0]);
setFmtExpect(fmtSource[0]);
fmtSource = getSrcRest();
if (fmtSource.replace(/(\r)?\n|\s/gm, '') === '') setFmtError('val');
}
function objPreHandler() {
chkFmtExpect(fmtSource[0]);
setFmtExpect(fmtSource[0]);
if (fmtSource[1] && fmtSource[1] === '}') {
fmtResult += '{}';
setFmtExpect('}');
fmtSource = getSrcRest(2);
} else {
curLevel++;
fmtResult += '{';
brkLine4Special();
fmtSource = getSrcRest();
}
}
function objEndHandler() {
curLevel--;
brkLine4Special('}');
chkFmtExpect(fmtSource[0]);
setFmtExpect(fmtSource[0]);
fmtSource = getSrcRest();
}
function arrPreHandler() {
chkFmtExpect(fmtSource[0]);
setFmtExpect(fmtSource[0]);
if (fmtSource[1] && fmtSource[1] === ']') {
fmtResult += '[]';
setFmtExpect(']');
fmtSource = getSrcRest(2);
} else {
curLevel++;
fmtResult += '[';
brkLine4Special();
fmtSource = getSrcRest();
}
}
function arrEndHandler() {
curLevel--;
brkLine4Special(']');
chkFmtExpect(fmtSource[0]);
setFmtExpect(fmtSource[0]);
fmtSource = getSrcRest();
}
function tupPreHandler() {
chkFmtExpect(fmtSource[0]);
setFmtExpect(fmtSource[0]);
if (fmtSource[1] && fmtSource[1] === ')') {
fmtResult += fmtOptions.isStrict ? '[]' : '()';
setFmtExpect(')');
fmtSource = getSrcRest(2);
} else {
curLevel++;
fmtResult += fmtOptions.isStrict ? '[' : '(';
brkLine4Special();
fmtSource = getSrcRest();
}
}
function tupEndHandler() {
curLevel--;
brkLine4Special(fmtOptions.isStrict ? ']' : ')');
chkFmtExpect(fmtSource[0]);
setFmtExpect(fmtSource[0]);
fmtSource = getSrcRest();
}
function unicHandler(unicMt) {
const rest = getSrcRest(unicMt.length);
const restIdx = unicMt.indexOf("'") > -1 ? getNextQuotaIndex("'", rest) : getNextQuotaIndex('"', rest);
chkFmtExpect('u');
const isProperty = exceptType === 'ost';
let uniqStr = '';
if (restIdx > -1) {
const cutIdx = restIdx + unicMt.length + 1;
uniqStr = fmtSource.substr(unicMt.length, cutIdx - unicMt.length - 1);
fmtResult += quoteSpecialStr(uniqStr, unicMt, isProperty);
setFmtExpect('u');
fmtSource = getSrcRest(cutIdx);
} else {
uniqStr = fmtSource.substr(unicMt.length);
fmtResult += quoteSpecialStr(uniqStr, unicMt, isProperty);
setFmtExpect('!');
fmtSource = '';
}
}
function numbHandler(numbMt) {
fmtResult += numbMt;
chkFmtExpect('n');
setFmtExpect('n');
fmtSource = getSrcRest(numbMt.length);
}
function boolHandler(boolMt) {
fmtResult += fmtOptions.isStrict ? boolMt.toLowerCase() : boolMt;
chkFmtExpect('b');
setFmtExpect('b');
fmtSource = getSrcRest(boolMt.length);
}
function nullHandler(nullMt) {
fmtResult += fmtOptions.isStrict ? 'null' : nullMt;
chkFmtExpect('N');
setFmtExpect('N');
fmtSource = getSrcRest(nullMt.length);
}
function otheHandler() {
const strMatch = fmtSource.match(/^[^\{\}\[\]\(\):,]*/);
const strMated = (strMatch && strMatch[0]) || '';
if (strMated) {
fmtResult += strMated;
chkFmtExpect('!');
fmtSource = getSrcRest(strMated.length);
}
}
function chkFmtExpect(sign) {
if (isSrcValid) {
switch (exceptType) {
case 'val':
if (':,}])!'.indexOf(sign) > -1) {
setFmtError('val');
}
break;
case 'ost':
if ('\'"unbN'.indexOf(sign) === -1) {
setFmtError('ost');
}
break;
case 'end':
const endBracket = getBracketPair(exceptSign);
if ([',', endBracket].indexOf(sign) === -1) {
setFmtError('end', endBracket);
}
break;
case 'col':
if (sign !== ':') {
setFmtError('col');
}
break;
}
}
}
function setFmtExpect(sign) {
switch (sign) {
case ':':
exceptType = 'val';
break;
case ',':
exceptSign === '{' ? (exceptType = 'ost') : (exceptType = 'val');
break;
case '{':
exceptSign = sign;
signsQueue += sign;
exceptType = 'ost';
break;
case '}':
signsQueue = signsQueue.substr(0, signsQueue.length - 1);
exceptSign = signsQueue.substr(-1);
exceptType = 'end';
break;
case '[':
exceptSign = sign;
signsQueue += sign;
exceptType = 'val';
break;
case ']':
signsQueue = signsQueue.substr(0, signsQueue.length - 1);
exceptSign = signsQueue.substr(-1);
exceptType = 'end';
break;
case '(':
exceptSign = sign;
signsQueue += sign;
exceptType = 'val';
break;
case ')':
signsQueue = signsQueue.substr(0, signsQueue.length - 1);
exceptSign = signsQueue.substr(-1);
exceptType = 'end';
break;
case 'u':
case 'n':
case 'b':
case 'N':
case '"':
case "'":
exceptType === 'ost' ? (exceptType = 'col') : (exceptType = 'end');
break;
}
}
function setFmtError(sign, brc = '') {
switch (sign) {
case 'war':
fmtType = 'warning';
break;
case 'scc':
fmtType = 'success';
break;
default:
fmtType = 'danger';
break;
}
if (['ost', 'col', 'val', 'end'].indexOf(sign) > -1) {
errFormat = true;
isSrcValid = false;
errExpect = brc;
errIndex = curIndex;
const rstTrailing = fmtResult
.substr(-20)
.replace(/^(\s|\n|\r\n)*/, '')
.replace(/(\n|\r\n)/gm, '\\n');
const srcLeading = fmtSource
.substr(0, 10)
.replace(/(\s|\n|\r\n)*$/, '')
.replace(/(\n|\r\n)/gm, '\\n');
errNear = `...${rstTrailing}>>>>>>${srcLeading}`;
}
fmtSign = sign;
message = MESSAGES_MAP[sign](curIndex, brc);
}
function setFmtStatus() {
if (isFmtError && !errIndex) {
setFmtError('err');
errFormat = true;
} else if (isSrcValid) {
if (signsQueue) {
const expBracket = getBracketPair(signsQueue.substr(-1));
setFmtError('end', expBracket);
} else {
setFmtError('scc');
}
}
fmtLines = curIndex;
}
/**
* =================================================================
* Util functions for the format.
* =================================================================
*/
function brkLine4Normal(str) {
if (!fmtOptions.isExpand) return str;
curIndex++;
return str + BREAK;
}
function brkLine4Special(str = '') {
if (!fmtOptions.isExpand) return (fmtResult += str);
curIndex++;
fmtResult += BREAK + getCurIndent() + str;
}
function quoteNormalStr(qtStr, quote, isFromAbnormal = false) {
const isEscape =
fmtOptions.isEscape &&
fmtOptions.keyQtMark === '"' &&
quote === '"' &&
(!isFromAbnormal || fmtOptions.isStrict);
qtStr = isFromAbnormal
? qtStr.replace(/(?!\\[b|f|n|\\|r|t|x|v|'|"|0])\\/gm, '\\\\')
: qtStr.replace(/\\/gm, '\\\\');
ESCAPES_MAP.forEach((esp) => (qtStr = qtStr.replace(esp.ptn, esp.str)));
const quote_ = isEscape ? `\\${quote}` : quote;
if (isEscape) qtStr = qtStr.replace(/\\/gm, '\\\\');
switch (quote) {
case '"':
qtStr = isEscape ? qtStr.replace(/"/gm, '\\\\\\"') : qtStr.replace(/"/gm, '\\"');
return quote_ + qtStr + quote_;
case "'":
qtStr = qtStr.replace(/'/gm, "\\'");
return quote_ + qtStr + quote_;
default:
return qtStr;
}
}
function quoteSpecialStr(qtStr, quoteMt, isProperty) {
const quote = isProperty ? fmtOptions.keyQtMark : fmtOptions.valQtMark;
qtStr = qtStr.replace(/(?!\\[b|f|n|\\|r|t|x|v|'|"|0])\\/gm, '');
qtStr = qtStr.replace(/\\\"/gm, '"');
qtStr = qtStr.replace(/\\\'/gm, "'");
qtStr = quoteNormalStr(qtStr, quote, true);
if (!fmtOptions.isStrict && quoteMt.length > 1) {
qtStr = quoteMt.substr(0, quoteMt.length - 1) + qtStr;
}
return qtStr;
}
function getSrcRest(len = 1) {
return fmtSource.length > len ? fmtSource.substr(len) : '';
}
function getNextQuotaIndex(quo, rest) {
for (let i = 0; i < rest.length; i++) {
if (rest[i] === quo) {
if (
i === 0 ||
rest[i - 1] !== '\\' ||
(rest[i - 1] === '\\' && rest[i - 2] === '\\' && rest[i - 3] !== '\\')
) {
return i;
}
}
}
return -1;
}
function getBaseIndent() {
let indent = '';
for (let i = 0; i < fmtOptions.indent; i++) {
indent += SPACE;
}
return indent;
}
function getCurIndent() {
let indent = '';
for (let i = 0; i < curLevel; i++) {
indent += baseIndent;
}
return indent;
}
function getBracketPair(braSign) {
const pre = ['{', '[', '('];
const end = ['}', ']', ')'];
const preIdx = pre.indexOf(braSign);
const endIdx = end.indexOf(braSign);
return preIdx > -1 ? end[preIdx] : pre[endIdx];
}
}
/**
* =================================================================
* UMD modules define.
* =================================================================
*/
if (typeof define === 'function' && define.amd) {
define(function () {
return fmt2json;
});
} else if (typeof exports === 'object') {
module.exports = fmt2json;
} else {
root.fmt2json = fmt2json;
}
})(this);
;
(