2017年4月21日金曜日

循環参照や参照の重複に対応したJSON化関数

先日公開したJSON化関数では、循環参照を含むオブジェクトを与えられると無限ループにはまってしまう問題がありました。また、複数のプロパティから単一のオブジェクトを参照しているケースではプロパティごとに異なるオブジェクトとして扱ってしまう問題がありました。

上記の問題を解消する方法を思いつき、サンプルを作成したので公開します。

まず、旧verのJSON化関数に「参照が重複しているプロパティを持つオブジェクト」を与えると以下のようになってしまいます。
※元々はobj.aとobj.bの中身は同じ配列だったので、例えば「obj.a.push(1)」を実行するとobj.bから参照している配列でも要素が増えるが、旧verのJSONで再現したものではobj.aとobj.bのそれぞれが別の配列を参照しているので、片方にpushしてももう片方には影響しない。再現できていない。


本日公開するJSON化関数では、元のオブジェクトの構造を正しく再現できるため、以下のようになります。


最新のJSON化関数のソースは以下。
LEN='length'
for0L = function(arr,fun){for(var i=0,L=arr[LEN],res;i<L;i++){if(res=fun(i,arr[i])){return res}}}
forIn = function(obj,fun){var name,res; for(name in obj){if(res=fun(name, obj[name])){return res}}}
インデント調整=function(f){ var s=f+'', 最終行=s.match(/([\t ]*)\}$/)[1]; return s.replace(new RegExp('\\n'+最終行,'g'),'\n') }
toJSON=function(){
var gt=function(o){return typeof(o)}, gc=function(o){return o.constructor}, objT={}, objC={}, fStr, rNg=/\n/g, nt='\n\t', main
objT[gt('')] = fStr = function(s){return '"'+s.replace(/\\/g,'\\\\').replace(/\r/g,'\\r').replace(/\n/g,'\\n').replace(/"/g,'\\"')+'"'}
objT[gt(0)] = objT[gt(true)] = objT[gt(gt.und)] = objC[gc(/1/)] = function(v){return v}
objC[gc(new Date())] = function(o){return 'new Date("'+o+'")'}
objC[gc(gc)] = function(o){return インデント調整(o)}
objC[gc({})] = function(o){
if(循環.名前push(o)){return '"参照の重複"'}
var a=[], i=0
forIn(o,function(n,v){ a[i++] = fStr(循環.名前=n)+':'+(main(v)+'').replace(rNg,nt) })
循環.pop()
return '{'+nt+a.join(','+nt)+'\n}'
}
objC[gc([])] = function(o){
if(循環.名前push(o)){return '"参照の重複"'}
var a0=[], s, a1=[], i=0
for0L(o,function(i,v){a0[循環.名前=i]=(main(v)+'').replace(rNg,nt)}), s='['+nt+a0.join(','+nt)+'\n]'
forIn(o,function(n,v){ !a0[n] && (a1[i++] = 'a['+fStr(循環.名前=n)+']='+main(v)) })
循環.pop()
return a1[LEN] ? (インデント調整(function(){
var a=sss
aaa111
return a
})+'()').replace(/sss/,s.replace(rNg,nt)).replace(/aaa111/,a1.join(nt)) : s
}
var 循環=function(){
var arr候補, arr確定, key, arr重複候補
var getKey=function(){return key.length ? '['+key.join('][')+']' : ''}
return {
init:function(){arr候補=[]; arr確定=[]; key=[]; arr重複候補=[], this.名前=''},
名前:'',
名前push:function(o){
this.名前 && key.push(fStr(this.名前))
// 追加した名前に対応するoと一致するものがarr候補にあるならtrueを返す
var fun=function(i,候補){return o==候補.o && arr確定.push({s:候補.key, d:getKey()}) && key.pop()}
if(key.length && (forIn(arr候補,fun) || forIn(arr重複候補,fun))){return true}
// 一致するものがなければ候補に追加する
arr候補.push({o:o, key:getKey()})
},
pop:function(){key.pop(), arr重複候補.push(arr候補.pop())},
getRes:function(objName){var a=[]; for0L(arr確定,function(i,o){ a.push(objName+o.d+' = '+objName+o.s) }); return a}
}
}()
main=function(o){ var t=gt(o); return o===null ? 'null' : objT[t] ? objT[t](o) : objC[gc(o)](o) }
return function(o){
循環.init()
var s=main(o), a=循環.getRes('o')
return !a.length ? s : 'function(){\n\tvar o='+s.replace(rNg,nt)+'\n\t'+a.join('\n\t')+'\n\treturn o\n}()'
}
}()
obj = {
a:[],
b:{
c:2,
e:{}
}
}
obj.c = obj
obj.b.d = obj.b
obj.b.e.a = obj.a
var str=toJSON(obj), obj0
eval('obj0 = '+str)
obj0.b.e.a.push(123)
/*
function(){
var o={
"a":[
],
"b":{
"c":2
"e":{
"a":"参照の重複"
},
"d":"参照の重複"
},
"c":"参照の重複"
}
o["b"]["e"]["a"] = o["a"]
o["b"]["d"] = o["b"]
o["c"] = o
return o
}()
function(){
var o={
"a":[
123
],
"b":{
"c":2
"e":{
"a":"参照の重複"
},
"d":"参照の重複"
},
"c":"参照の重複"
}
o["b"]["e"]["a"] = o["a"]
o["b"]["d"] = o["b"]
o["c"] = o
return o
}()
*/
WScript.Echo(str+'\n\n'+toJSON(obj0))
view raw toJSON.js hosted with ❤ by GitHub

0 件のコメント:

コメントを投稿