diff --git a/serialize2.js b/serialize2.js index f250db2..80da5ab 100644 --- a/serialize2.js +++ b/serialize2.js @@ -17,6 +17,8 @@ var NEG_INFINITY = '-Infinity' var RECURSIVE = '' +var FUNCTION = '' + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -48,7 +50,7 @@ var debug = { // string - string to use for indenting // // -// serialize(obj, base_path, seen, indent, depth) +// _serialize(obj, base_path, seen, indent, depth, functions) // -> str // // @@ -96,38 +98,31 @@ var debug = { // ] // // -// XXX add function support... -// XXX need to destinguish between map key and value in path... // XXX BUG: using non-whitespace as indent breaks the depth of the first // or last elements in sequences // ...breaks .trim*() in Map/Set/Object... -var serialize = -module.serialize = -function(obj, path=[], seen=new Map(), indent, depth=0){ - // args... - var args = [...arguments].slice(1) - if(typeof(args[0]) == 'number' - || typeof(args[0]) == 'string'){ - indent = args.shift() } - indent = - typeof(indent) == 'number' ? - ' '.repeat(indent) - : indent - if(typeof(args[0]) == 'number'){ - depth = args.shift() } - ;[path, seen] = args - path ??= [] - seen ??= new Map() - - +var _serialize = +module._serialize = +function(obj, path=[], seen=new Map(), indent, depth=0, functions){ // recursive... var p = seen.get(obj) if(p != null){ - // NOTE: serialize(..) is always printed flat here, regardless of indent/depth... - return RECURSIVE.replace('%', serialize(p)) } + // NOTE: _serialize(..) is always printed flat here, regardless of indent/depth... + return RECURSIVE.replace('%', _serialize(p)) } - // XXX functions... - // XXX + // functions... + // NOTE: we are storing function length to avoid parsing the function... + // NOTE: storing length is a potential attack vector... + if(typeof(obj) == 'function'){ + seen.set(obj, path) + // if functions array is given add function to it and store its + // index in the serialized data... + if(functions != null){ + functions.push(obj) + obj = functions.length-1 } + var s = '('+ obj.toString() +')' + return FUNCTION + .replace('%', s.length +','+ s) } // atomics... if(obj === null){ @@ -157,20 +152,20 @@ function(obj, path=[], seen=new Map(), indent, depth=0){ for(var i=0; i < obj.length; i++){ elems.push( i in obj ? - serialize(obj[i], [...path, i], seen, indent, depth+1) + _serialize(obj[i], [...path, i], seen, indent, depth+1, functions) : EMPTY) } } else if(obj instanceof Map){ pre = 'Map([' post = '])' elems = [ - serialize([...obj], path, seen, indent, depth) + _serialize([...obj], path, seen, indent, depth, functions) .slice(1, -1) .trim() ] } else if(obj instanceof Set){ pre = 'Set([' post = '])' elems = [ - serialize([...obj], path, seen, indent, depth) + _serialize([...obj], path, seen, indent, depth, functions) .slice(1, -1) .trim() ] } else { @@ -180,7 +175,7 @@ function(obj, path=[], seen=new Map(), indent, depth=0){ elems.push(`${ JSON.stringify(k) }:${ indent != null ? ' ' : '' }${ - serialize(v, [...path, k], seen, indent, depth+1) + _serialize(v, [...path, k], seen, indent, depth+1, functions) // relevant for pretty-printing only... .trimLeft() }`) } } @@ -198,6 +193,11 @@ function(obj, path=[], seen=new Map(), indent, depth=0){ return pre+ elems.join(join) +post } +// user interface... +var serialize = +module.serialize = +function(obj, indent, depth=0, functions){ + return _serialize(obj, [], new Map(), indent, depth, functions) } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -234,6 +234,8 @@ module.eJSON = { '': 'empty', ''){ + res = eval?.(code) } } + + if(str.slice(i, i+2) != ']>'){ + this.error('Expected "]>" got "'+ str.slice(i, i+2) +'"', str, i, line) } + return [res, i+2, line] }, value: function(state, path, str, i=0, line=0){ //debug.lex('value', str, i, line) @@ -569,7 +598,7 @@ module.eJSON = { var context = 8 var pre = Math.max(i-context, 0) var post = Math.min(i+context, str.length) - this.error(`Unexpected: ${ str[i] }`, str, i, line) } + this.error(`Unexpected: ${ str.slice(i, i+10) }`, str, i, line) } // value... if(typeof(handler) != 'string'){ @@ -578,10 +607,10 @@ module.eJSON = { return this[handler](state, path, match, str, i, line) }, - parse: function(str){ + parse: function(str, functions){ // stage 1: build the object... - var state = {} + var state = {functions} var res = this.value(state, [], str)[0] // stage 2: link the recursive structures... @@ -594,14 +623,13 @@ module.eJSON = { var deserialize = module.deserialize = -function(str){ - return eJSON.parse(str) } +function(str, functions){ + return eJSON.parse(str, functions) } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // utils... -// XXX see ./serialize.js var deepCopy = module.deepCopy = @@ -610,6 +638,14 @@ function(obj){ serialize(obj)) } +var semiDeepCopy = +module.semiDeepCopy = +function(obj){ + var funcs = [] + return deserialize( + serialize(obj, null, 0, funcs), + funcs) } + /********************************************************************** diff --git a/test.js b/test.js index 6f67fdd..e19957d 100755 --- a/test.js +++ b/test.js @@ -28,6 +28,17 @@ var ejson = false // XXX test whitespace handling... var setups = test.Setups({ + 'true': function(assert){ + return ['true', json] }, + 'false': function(assert){ + return ['false', json] }, + 'null': function(assert){ + return ['null', json] }, + 'undefined': function(assert){ + return ['undefined'] }, + 'NaN': function(assert){ + return ['NaN'] }, + number: function(assert){ return ['123', json] }, 'number-neg': function(assert){ @@ -49,20 +60,10 @@ var setups = test.Setups({ return ['Infinity'] }, nInfinity: function(assert){ return ['-Infinity'] }, + // XXX also test diffrerent quotations... string: function(assert){ return ['"string"', json] }, - 'true': function(assert){ - return ['true', json] }, - 'false': function(assert){ - return ['false', json] }, - - 'null': function(assert){ - return ['null', json] }, - 'undefined': function(assert){ - return ['undefined'] }, - 'NaN': function(assert){ - return ['NaN'] }, 'array-empty': function(assert){ return ['[]', json] }, @@ -74,8 +75,6 @@ var setups = test.Setups({ return ['[,"a"]'] }, 'array-sparse-d': function(assert){ return ['[,1,,,2,]'] }, - 'array-recursive': function(assert){ - return ['[]'] }, 'object-empty': function(assert){ return ['{}', json] }, @@ -86,7 +85,21 @@ var setups = test.Setups({ 'map-empty': function(assert){ return ['Map([])'] }, - // XXX + 'function': function(assert){ + return [''] }, + + // recursive... + 'array-recursive': function(assert){ + return ['[]'] }, + 'object-recursive': function(assert){ + return ['{"r":}'] }, + 'set-recursive': function(assert){ + return ['Set([])'] }, + 'map-recursive-key': function(assert){ + return ['Map([[,"value"]])'] }, + 'map-recursive-value': function(assert){ + return ['Map([["key",]])'] }, + }) test.Modifiers({ @@ -95,32 +108,32 @@ test.Modifiers({ 'array-stuffed': function(assert, [setup, json]){ return [ eJSON.serialize( - [ eJSON.deserialize(setup) ] ), + [ eJSON.deserialize(setup, true) ] ), json, ] }, 'object-stuffed': function(assert, [setup, json]){ return [ eJSON.serialize( - { key: eJSON.deserialize(setup) } ), + { key: eJSON.deserialize(setup, true) } ), json, ] }, // NOTE: these explicitly remove JSON compatibility regardless of input... 'set-stuffed': function(assert, [setup]){ return [ eJSON.serialize( - new Set([ eJSON.deserialize(setup) ]) ), + new Set([ eJSON.deserialize(setup, true) ]) ), ejson, ] }, 'map-key-stuffed': function(assert, [setup]){ return [ eJSON.serialize( - new Map([[eJSON.deserialize(setup), "value"]]) ), + new Map([[eJSON.deserialize(setup, true), "value"]]) ), ejson, ] }, 'map-value-stuffed': function(assert, [setup]){ return [ eJSON.serialize( - new Map([["key", eJSON.deserialize(setup)]]) ), + new Map([["key", eJSON.deserialize(setup, true)]]) ), ejson, ] }, }) @@ -129,14 +142,14 @@ test.Tests({ 'self-apply': function(assert, [setup]){ var res assert( - setup == (res = eJSON.serialize( eJSON.deserialize(setup) ) ), + setup == (res = eJSON.serialize( eJSON.deserialize(setup, true) ) ), 'serialize(deserialize( setup )) == setup: expected:', setup, 'got:', res) }, 'json-caopatiility': function(assert, [setup, json], skipTest){ if(!json){ skipTest() return } - var obj = eJSON.deserialize(setup) + var obj = eJSON.deserialize(setup, true) assert( JSON.stringify(obj) == eJSON.serialize(obj) ) }, })