TypeScriptで2つのJSONを比較して同じものかを調べるコードを書いてみた
2023/11/03 09:22
誤り発見です。
オブジェクトのある部分の値が共にnullの場合、キーが存在しないと誤判定されます。
例:
obj1={"aa":null}
obj2={"aa":null}
これはobj1に存在するものがobj2に存在するかの判定を”==undefined”で判定している結果です。
値がnullの場合、”==undefined”の判定はtrueになりますので、nullのケースを除外する必要があります。
前の投稿から時間がかかりましたが2つのJSONを比較して同じものかを調べるコードを書いてみました。
注意点としては、
・ionicでhome.page.ts内で記述したので"function"が記載されていない
・javasciptで使用するならば、型の宣言関係("as any"とか":0bject"とか)を除外すれば動くかも?
(こちらはテストしていないです)
それなりにテストはしましたが、実際に使われる場合は十分なテストを行って問題があれば修正してください。
戻り値のオブジェクトrtnObj["result"]がtrueならば等しくfalseならば異なるJSONです。
最初に差異が見つかった場所は、rtnObj["AccessKey"]で配列として返します。
例えば、
obj1={"a":[1,2,3,4],"b":{},"c":[5,7]}
obj2={"c":[5,7],"a":[1,2,99,4],"b":{}}
ならば
rtnObj["AccessKey"]= ["a","2"]
となり、
obj1["a"]["2"]=3
obj2["a"]["2"]=99
で異なる事が判ります。
(注意点:JSONのキーの部分は配列のインデックスである数値も文字列して扱われます。ですので配列かどうかのチェックは必須です。)
-----------------------------------
fnEquql2JSON(obj1:any, obj2:any, strict=true):object{ //strictはオプション引数。falseならば文字列"1"と数値1は同じものとして扱う。
let arrAccessKey=[] as any;
const typeObj1=typeof(obj1)
const typeObj2=typeof(obj2)
const nullObj1=(obj1==null)
const nullObj2=(obj2==null)
const arrayObj1=Array.isArray(obj1)
const arrayObj2=Array.isArray(obj2)
let rtnObj={
"result":true, "type of obj1":typeObj1, "type of obj2":typeObj2,
"obj1 is null":nullObj1, "obj2 is null":nullObj2,
"obj1 is Array":arrayObj1, "obj2 is Array":arrayObj2,
"strict":strict,
"desc":"", "AccessKey":arrAccessKey,"loop counter":0
};
try {
const isTest=false;
let cntloop=0;
//初期チェック
//単純比較(ともにnullなども含む)
if(strict){
if(obj1===obj2){
rtnObj["result"]=true;
rtnObj["desc"]='obj1===obj2';
return rtnObj;
}
}else{
if(obj1==obj2){
rtnObj["result"]=true;
rtnObj["desc"]='obj1==obj2';
return rtnObj;
}
}
switch (typeObj1) {
case "string":
case "number":
case "boolean":
case "undefined":
case "bigint":
case "symbol": //https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Symbol
case "function":
if(typeObj1==typeObj2){
rtnObj["result"]=false;
rtnObj["desc"]="obj1 and obj2 are different values";
return rtnObj;
}else{
rtnObj["result"]=false;
rtnObj["desc"]="obj1 and obj2 are different types";
return rtnObj;
}
case "object":
//nullで等しいケースは除外済みだが片方のみnullはありえる
if(nullObj1||nullObj2){
rtnObj["result"]=false; //等しいケースは除外済み
rtnObj["desc"]="One is null, the other is not";
return rtnObj;
}
//Array?
if(arrayObj1!=arrayObj2){
rtnObj["result"]=false; //等しいケースは除外済み
rtnObj["desc"]="One is an array, the other is an Object";
return rtnObj;
}
//key
if(Object.keys(obj1).length!=Object.keys(obj2).length){
rtnObj["result"]=false;
rtnObj["desc"]="Different number of keys";
return rtnObj;
}
if(Object.keys(obj1).length==0){ //少なくとも一つは要素を持つ
rtnObj["result"]=true;
rtnObj["desc"]="Note: This object has no keys";
return rtnObj;
}
break;
default: //予期せぬエラー
rtnObj["result"]=false;
rtnObj["desc"]="switch (typeObj1) {";
return rtnObj;
}
//各階層には複数あるいは一つのBlockが存在する。チェックするのは一度に一つのみ
let key1=Object.keys(obj1);
let chkBlockIndex=0; //Keyのないケースは初期チェックで除外済み(等しい、等しくないにかかわらず)
let BlockLength=key1.length; //隣接のBlockの数(経路が変われば異なる算出が必要)
let arrCheckInfo=[]as any;
let arrTemp=[obj1, obj2, chkBlockIndex, BlockLength, key1];
//obj1, obj2 : 比較すべきobjあるいはArrayあるいは値
//chkBlockIndex, BlockLength : 検査するブロック内インデックスとブロックの保持する項目の数
//key1 : ブロック各項目にアクセスするキー配列(ex. obj1[key1[chkBlockIndex]])
//上記項目を階層ごとに作成してarrCheckInfoにpush
arrCheckInfo.push(arrTemp);
//初回のキーの存在チェック(obj2)
//if(obj2[arrCheckInfo[0][4][arrCheckInfo[0][2]]]==undefined){前書き参照
if(obj2[arrCheckInfo[0][4][arrCheckInfo[0][2]]]==undefined && obj2[arrCheckInfo[0][4][arrCheckInfo[0][2]]]!=null){
rtnObj["result"]=false;
rtnObj["desc"]="obj2 does not have key "+arrCheckInfo[0][4][arrCheckInfo[0][2]];
arrAccessKey=[];
rtnObj["AccessKey"]=arrAccessKey;
return rtnObj;
}
let lastindex=arrCheckInfo.length-1;
//Loop
let flg1stLoop=true; //最初のループ
let flgLoopNext=true; //falseになるまでLoop
do {
lastindex=arrCheckInfo.length-1;
rtnObj["loop counter"]++;
//for test
if(isTest){
cntloop++;
if(cntloop>5){
rtnObj["result"]=false;
rtnObj["desc"]="for test : cntloop>5";
return rtnObj;
}
}
if(arrCheckInfo[lastindex][2]>arrCheckInfo[lastindex][3]-1 && !flg1stLoop){
//初回のループで無く、このブロックの要素の最後がチェック済み
if(lastindex<=0){ //flg1stLoop=falseなので初回ではない(初回ブロック数1はあり得る)
//最上位の階層ならばブロックは一つのみ、ゆえにすべてのチェックが終了
rtnObj["result"]=true;
rtnObj["desc"]="Check completed!";
flgLoopNext=false; //チェック終了(obj1とobj2は等しい)
}else{
//同じ階層に次のブロックがなく最上位の階層でなければ、現在の階層をpopして一つ階層を上がり次のブロックの検査
arrCheckInfo.pop();
arrCheckInfo[lastindex-1][2]++; //次のループへ
}
}else{
//このブロックの比較すべき要素をチェック
let elmtemp1 : any //比較すべき要素1
let elmtemp2 : any //比較すべき要素2
arrCheckInfo.map((Element:any,index:number)=>{
if(index==0){
elmtemp1=obj1[Element[4][Element[2]]]; //インデックス番目(Element[2])のKey(Element[4])
elmtemp2=obj2[Element[4][Element[2]]];
}else{
elmtemp1=elmtemp1[Element[4][Element[2]]]; //インデックス番目(Element[2])のKey(Element[4])
elmtemp2=elmtemp2[Element[4][Element[2]]];
}
})
//undefined obj1にあるキーがobj2に存在しない
//if(elmtemp2==undefined){前書き参照
if(elmtemp2==undefined && elmtemp2!=null){
rtnObj["result"]=false;
rtnObj["desc"]="obj2 does not have key "+arrCheckInfo[lastindex][4][arrCheckInfo[lastindex][2]];
arrAccessKey=[];
arrCheckInfo.map((el:any)=>{arrAccessKey.push(el[4][el[2]])});
rtnObj["AccessKey"]=arrAccessKey;
return rtnObj;
}
const typetemp1=typeof(elmtemp1);
//要素チェック
switch (typetemp1) {
case "string":
case "number":
case "boolean":
case "undefined":
if(strict){
if(elmtemp1!==elmtemp2){
rtnObj["result"]=false;
rtnObj["desc"]="property1!==property2";
arrAccessKey=[];
arrCheckInfo.map((el:any)=>{arrAccessKey.push(el[4][el[2]])});
rtnObj["AccessKey"]=arrAccessKey;
return rtnObj;
}
}else{
if(elmtemp1!=elmtemp2){
rtnObj["result"]=false;
rtnObj["desc"]="property1!=property2";
arrAccessKey=[];
arrCheckInfo.map((el:any)=>{arrAccessKey.push(el[4][el[2]])});
rtnObj["AccessKey"]=arrAccessKey;
return rtnObj;
}
}
//次の要素の検査へ
arrCheckInfo[lastindex][2]++; //次のループへ
break;
case "bigint":
case "symbol": //https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Symbol
case "function":
//この3種類はJSONの仕様上ありえない
rtnObj["result"]=false;
rtnObj["desc"]="unexpected error : typeof(elmtemp1) = "+elmtemp1
arrAccessKey=[];
arrCheckInfo.map((el:any)=>{arrAccessKey.push(el[4][el[2]])});
rtnObj["AccessKey"]=arrAccessKey;
return rtnObj;
case "object":
//null
if((elmtemp1==null)!=(elmtemp2==null)){
rtnObj["result"]=false;
rtnObj["desc"]="One is null, the other is not";
arrAccessKey=[];
arrCheckInfo.map((el:any)=>{arrAccessKey.push(el[4][el[2]])});
rtnObj["AccessKey"]=arrAccessKey;
return rtnObj;
}else{
if(elmtemp1==null){ //どちらもnull
//次の要素の検査へ
arrCheckInfo[lastindex][2]++; //次のループへ
break;
}
}
//Array
if(Array.isArray(elmtemp1)!=Array.isArray(elmtemp2)){
rtnObj["result"]=false;
rtnObj["desc"]="One is Array, the other is not";
arrAccessKey=[];
arrCheckInfo.map((el:any)=>{arrAccessKey.push(el[4][el[2]])});
rtnObj["AccessKey"]=arrAccessKey;
return rtnObj;
}
//Key
const keyTemp1=Object.keys(elmtemp1);
const keyLength1=keyTemp1.length;
if(keyLength1!=Object.keys(elmtemp2).length){
rtnObj["result"]=false;
rtnObj["desc"]="The number of keys in obj1 is different from the number of keys in obj2";
arrAccessKey=[];
arrCheckInfo.map((el:any)=>{arrAccessKey.push(el[4][el[2]])});
rtnObj["AccessKey"]=arrAccessKey;
return rtnObj;
}
//arrCheckInfoに追加して次のループへ
const arrTemp=[
elmtemp1,
elmtemp2,
0,
keyLength1,
keyTemp1
];
arrCheckInfo.push(arrTemp);
break;
default: //予期せぬエラー
rtnObj["result"]=false;
rtnObj["desc"]="unexpected error"
arrAccessKey=[];
arrCheckInfo.map((el:any)=>{arrAccessKey.push(el[4][el[2]])});
rtnObj["AccessKey"]=arrAccessKey;
return rtnObj;
}
}
if(flg1stLoop){flg1stLoop=false};
} while (flgLoopNext);
return rtnObj;
} catch (error) {
rtnObj["result"]=false;
rtnObj["desc"]="try catch error : "+String(error)
return rtnObj;
}
}




