You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
261 lines
6.8 KiB
261 lines
6.8 KiB
const util = require('./util') |
|
|
|
module.exports = function stringify (value, replacer, space) { |
|
const stack = [] |
|
let indent = '' |
|
let propertyList |
|
let replacerFunc |
|
let gap = '' |
|
let quote |
|
|
|
if ( |
|
replacer != null && |
|
typeof replacer === 'object' && |
|
!Array.isArray(replacer) |
|
) { |
|
space = replacer.space |
|
quote = replacer.quote |
|
replacer = replacer.replacer |
|
} |
|
|
|
if (typeof replacer === 'function') { |
|
replacerFunc = replacer |
|
} else if (Array.isArray(replacer)) { |
|
propertyList = [] |
|
for (const v of replacer) { |
|
let item |
|
|
|
if (typeof v === 'string') { |
|
item = v |
|
} else if ( |
|
typeof v === 'number' || |
|
v instanceof String || |
|
v instanceof Number |
|
) { |
|
item = String(v) |
|
} |
|
|
|
if (item !== undefined && propertyList.indexOf(item) < 0) { |
|
propertyList.push(item) |
|
} |
|
} |
|
} |
|
|
|
if (space instanceof Number) { |
|
space = Number(space) |
|
} else if (space instanceof String) { |
|
space = String(space) |
|
} |
|
|
|
if (typeof space === 'number') { |
|
if (space > 0) { |
|
space = Math.min(10, Math.floor(space)) |
|
gap = ' '.substr(0, space) |
|
} |
|
} else if (typeof space === 'string') { |
|
gap = space.substr(0, 10) |
|
} |
|
|
|
return serializeProperty('', {'': value}) |
|
|
|
function serializeProperty (key, holder) { |
|
let value = holder[key] |
|
if (value != null) { |
|
if (typeof value.toJSON5 === 'function') { |
|
value = value.toJSON5(key) |
|
} else if (typeof value.toJSON === 'function') { |
|
value = value.toJSON(key) |
|
} |
|
} |
|
|
|
if (replacerFunc) { |
|
value = replacerFunc.call(holder, key, value) |
|
} |
|
|
|
if (value instanceof Number) { |
|
value = Number(value) |
|
} else if (value instanceof String) { |
|
value = String(value) |
|
} else if (value instanceof Boolean) { |
|
value = value.valueOf() |
|
} |
|
|
|
switch (value) { |
|
case null: return 'null' |
|
case true: return 'true' |
|
case false: return 'false' |
|
} |
|
|
|
if (typeof value === 'string') { |
|
return quoteString(value, false) |
|
} |
|
|
|
if (typeof value === 'number') { |
|
return String(value) |
|
} |
|
|
|
if (typeof value === 'object') { |
|
return Array.isArray(value) ? serializeArray(value) : serializeObject(value) |
|
} |
|
|
|
return undefined |
|
} |
|
|
|
function quoteString (value) { |
|
const quotes = { |
|
"'": 0.1, |
|
'"': 0.2, |
|
} |
|
|
|
const replacements = { |
|
"'": "\\'", |
|
'"': '\\"', |
|
'\\': '\\\\', |
|
'\b': '\\b', |
|
'\f': '\\f', |
|
'\n': '\\n', |
|
'\r': '\\r', |
|
'\t': '\\t', |
|
'\v': '\\v', |
|
'\0': '\\0', |
|
'\u2028': '\\u2028', |
|
'\u2029': '\\u2029', |
|
} |
|
|
|
let product = '' |
|
|
|
for (let i = 0; i < value.length; i++) { |
|
const c = value[i] |
|
switch (c) { |
|
case "'": |
|
case '"': |
|
quotes[c]++ |
|
product += c |
|
continue |
|
|
|
case '\0': |
|
if (util.isDigit(value[i + 1])) { |
|
product += '\\x00' |
|
continue |
|
} |
|
} |
|
|
|
if (replacements[c]) { |
|
product += replacements[c] |
|
continue |
|
} |
|
|
|
if (c < ' ') { |
|
let hexString = c.charCodeAt(0).toString(16) |
|
product += '\\x' + ('00' + hexString).substring(hexString.length) |
|
continue |
|
} |
|
|
|
product += c |
|
} |
|
|
|
const quoteChar = quote || Object.keys(quotes).reduce((a, b) => (quotes[a] < quotes[b]) ? a : b) |
|
|
|
product = product.replace(new RegExp(quoteChar, 'g'), replacements[quoteChar]) |
|
|
|
return quoteChar + product + quoteChar |
|
} |
|
|
|
function serializeObject (value) { |
|
if (stack.indexOf(value) >= 0) { |
|
throw TypeError('Converting circular structure to JSON5') |
|
} |
|
|
|
stack.push(value) |
|
|
|
let stepback = indent |
|
indent = indent + gap |
|
|
|
let keys = propertyList || Object.keys(value) |
|
let partial = [] |
|
for (const key of keys) { |
|
const propertyString = serializeProperty(key, value) |
|
if (propertyString !== undefined) { |
|
let member = serializeKey(key) + ':' |
|
if (gap !== '') { |
|
member += ' ' |
|
} |
|
member += propertyString |
|
partial.push(member) |
|
} |
|
} |
|
|
|
let final |
|
if (partial.length === 0) { |
|
final = '{}' |
|
} else { |
|
let properties |
|
if (gap === '') { |
|
properties = partial.join(',') |
|
final = '{' + properties + '}' |
|
} else { |
|
let separator = ',\n' + indent |
|
properties = partial.join(separator) |
|
final = '{\n' + indent + properties + ',\n' + stepback + '}' |
|
} |
|
} |
|
|
|
stack.pop() |
|
indent = stepback |
|
return final |
|
} |
|
|
|
function serializeKey (key) { |
|
if (key.length === 0) { |
|
return quoteString(key, true) |
|
} |
|
|
|
const firstChar = String.fromCodePoint(key.codePointAt(0)) |
|
if (!util.isIdStartChar(firstChar)) { |
|
return quoteString(key, true) |
|
} |
|
|
|
for (let i = firstChar.length; i < key.length; i++) { |
|
if (!util.isIdContinueChar(String.fromCodePoint(key.codePointAt(i)))) { |
|
return quoteString(key, true) |
|
} |
|
} |
|
|
|
return key |
|
} |
|
|
|
function serializeArray (value) { |
|
if (stack.indexOf(value) >= 0) { |
|
throw TypeError('Converting circular structure to JSON5') |
|
} |
|
|
|
stack.push(value) |
|
|
|
let stepback = indent |
|
indent = indent + gap |
|
|
|
let partial = [] |
|
for (let i = 0; i < value.length; i++) { |
|
const propertyString = serializeProperty(String(i), value) |
|
partial.push((propertyString !== undefined) ? propertyString : 'null') |
|
} |
|
|
|
let final |
|
if (partial.length === 0) { |
|
final = '[]' |
|
} else { |
|
if (gap === '') { |
|
let properties = partial.join(',') |
|
final = '[' + properties + ']' |
|
} else { |
|
let separator = ',\n' + indent |
|
let properties = partial.join(separator) |
|
final = '[\n' + indent + properties + ',\n' + stepback + ']' |
|
} |
|
} |
|
|
|
stack.pop() |
|
indent = stepback |
|
return final |
|
} |
|
}
|
|
|