123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261 |
- 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
- }
- }
|