1 exports.encode = encode
2 exports.decode = decode
5 function encode (obj, indent) {
6 var deep = arguments[2]
7 if (!indent) indent = " "
9 if (obj instanceof String ||
10 Object.prototype.toString.call(obj) === "[object String]") {
14 if (obj instanceof Number ||
15 Object.prototype.toString.call(obj) === "[object Number]") {
19 // take out the easy ones.
23 if (obj.indexOf("\n") !== -1) {
24 return "|\n" + indent + obj.split(/\r?\n/).join("\n"+indent)
30 return obj.toString(10)
33 return encode(obj.toString(), indent, true)
41 // at this point we know it types as an object
44 if (obj instanceof Date ||
45 Object.prototype.toString.call(obj) === "[object Date]") {
46 return JSON.stringify("[Date " + obj.toISOString() + "]")
49 if (obj instanceof RegExp ||
50 Object.prototype.toString.call(obj) === "[object RegExp]") {
51 return JSON.stringify(obj.toString())
54 if (obj instanceof Boolean ||
55 Object.prototype.toString.call(obj) === "[object Boolean]") {
59 if (seen.indexOf(obj) !== -1) {
64 if (typeof Buffer === "function" &&
65 typeof Buffer.isBuffer === "function" &&
66 Buffer.isBuffer(obj)) return obj.inspect()
68 if (obj instanceof Error) {
69 var o = { name: obj.name
70 , message: obj.message
73 if (obj.code) o.code = obj.code
74 if (obj.errno) o.errno = obj.errno
75 if (obj.type) o.type = obj.type
81 if (Array.isArray(obj)) {
82 var out = "\n" + indent + "- " +obj.map(function (item) {
83 return encode(item, indent + " ", true)
84 }).join("\n"+indent + "- ")
89 var keys = Object.keys(obj)
90 , niceKeys = keys.map(function (k) {
91 return (k.match(/^[a-zA-Z0-9_]+$/) ? k : JSON.stringify(k)) + ": "
93 //console.error(keys, niceKeys, obj)
94 var maxLength = Math.max.apply(Math, niceKeys.map(function (k) {
97 //console.error(niceKeys, maxLength)
99 var spaces = new Array(maxLength + 1).join(" ")
101 if (!deep) indent += " "
102 out = "\n" + indent + keys.map(function (k, i) {
103 var niceKey = niceKeys[i]
104 return niceKey + spaces.substr(niceKey.length)
105 + encode(obj[k], indent + " ", true)
106 }).join("\n" + indent)
111 if (!deep) seen.length = 0
115 function decode (str) {
118 , dateRe = /^\[Date ([0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}(?::[0-9]{2})?(?:\.[0-9]{3})?(?:[A-Z]+)?)\]$/
120 if (v === "~") return null
123 var jp = JSON.parse(str)
129 typeof jp === "string" &&
130 (d = jp.match(dateRe)) &&
131 (d = Date.parse(d[1]))) {
135 if (typeof jp === "boolean") return jp
136 if (v && !isNaN(v)) return parseInt(v, 10)
138 // something interesting.
139 var lines = str.split(/\r?\n/)
140 // check if it's some kind of string or something.
141 // if the first line is > or | then it's a wrapping indented string.
142 // if the first line is blank, and there are many lines,
143 // then it's an array or object.
144 // otherwise, it's just ""
145 var first = lines.shift().trim()
146 if (lines.length) lines = undent(lines)
149 return lines.join("\n")
151 return lines.join("\n").split(/\n{2,}/).map(function (l) {
152 return l.split(/\n/).join(" ")
155 if (!lines.length) return first
157 // the first line will be either "- value" or "key: value"
158 return lines[0].charAt(0) === "-" ? decodeArr(lines) : decodeObj(lines)
162 function decodeArr (lines) {
166 for (var i = 0, l = lines.length; i < l; i ++) {
167 // if it starts with a -, then it's a new thing
169 if (line.charAt(0) === "-") {
171 out[key ++] = decode(val.join("\n"))
174 val.push(line.substr(1).trim())
175 } else if (line.charAt(0) === " ") {
180 out[key ++] = decode(val.join("\n"))
185 function decodeObj (lines) {
190 for (var i = 0, l = lines.length; i < l; i ++) {
192 if (line.charAt(0) === " ") {
198 out[key] = decode(val.join("\n"))
201 // parse out the quoted key
203 if (line.charAt(0) === "\"") {
204 for (var ii = 1, ll = line.length, esc = false; ii < ll; ii ++) {
205 var c = line.charAt(ii)
208 } else if (c === "\"" && !esc) {
212 key = JSON.parse(line.substr(0, ii + 1))
213 line = line.substr(ii + 1)
214 first = line.substr(line.indexOf(":") + 1).trim()
216 var kv = line.split(":")
218 first = kv.join(":").trim()
220 // now we've set a key, and "first" has the first line of the value.
221 val.push(first.trim())
223 if (val.length) out[key] = decode(val.join("\n"))
227 function undent (lines) {
228 var i = lines[0].match(/^\s*/)[0].length
229 return lines.map(function (line) {
230 return line.substr(i)
235 // XXX Turn this into proper tests.
236 if (require.main === module) {
237 var obj = [{"bigstring":new Error().stack}
238 ,{ar:[{list:"of"},{some:"objects"}]}
240 ,{"super huge string":new Error().stack}
243 Date.prototype.toJSON = function (k, val) {
244 console.error(k, val, this)
245 return this.toISOString() + " (it's a date)"
248 var enc = encode(obj)
250 , encDec = encode(dec)
252 console.error(JSON.stringify({ obj : obj
253 , enc : enc.split(/\n/)
254 , dec : dec }, null, 2), encDec === enc)
257 , encNum = encode(num)
258 , decEncNum = decode(encNum)
259 console.error([num, encNum, decEncNum])