[js] 2개의 JSON이 같은 값인지 비교하기

개요

JSON 등 복잡한 객체의 모든 값이 일치하는지를 비교하는건 굉장히 귀찮은 일입니다. 자바스크립트를 통해 서로 다른 두개의 변수가 값으로 일치하는지를 판별하려는 경우 문자열이나 숫자는 그저 비교하면 되지만 오브젝트나 배열은 까다로운 상황이 됩니다. 경우에 맞춰 재귀적인 방법으로 처리해보죠.

 

기본형의 비교

기본형에 해당되는 값은 문자, 숫자, 불린, undefined, null 입니다. 이들은 그저 비교하면 되는데 이들을 판별하기에 가장 적당한 연산자는 typeof 입니다. 한 가지 주의할 점은 null 의 경우 typeof 에서 object 로 나온다는 점입니다. 따라서 이를 이용하면 다음과 같이 정리할 수 있습니다.

var compare = function( a, b ){
	var type;

	//a의 타입
	type = typeof a;

	// 오브젝트인 경우만 특수처리
	if( type == "object" ){

		// 하지만 null은 단순비교하면 됨
		if( a === null ) return a === b;

		// 그 외 오브젝트 간의 비교
	}

	//기본형은 그저 일치만 확인하면 됨
	return a === b;
};

//사용하기
console.log( compare( "test", "test" ) );

일단 이 구조를 이용해 객체 외적인 녀석들 전부 해결할 수 있습니다.

배열의 경우

배열을 자바스크립트에서 탐지하는 방법은 여러가지가 있습니다만 새 표준인 Array.isArray 를 활용하면 확실하고 그럭저럭 splice 메서드가 존재하는지 체크하거나 생성자가 Array 인지 검사하는 방법도 활용할 수 있습니다.

일단 배열인 경우로 분리되면 길이가 같은지를 가볍게 걸러내고 그 뒤부터는 하나하나를 검사해야합니다.

이 때 하나하나의 요소를 비교하는 방식이 재귀에 의존하게 됩니다.

var compare = function( a, b ){
	var type = typeof a, i, j;

	if( type == "object" ){
		if( a === null ) return a === b;
		else if( Array.isArray(a) ){ //배열인 경우

			//기본필터
			if( !Array.isArray(b) || a.length != b.length ) return false;

			//요소를 순회하면서 재귀적으로 검증한다.
			for( i = 0, j = a.length ; i < j ; i++ ){
				if(!compare(a[i], b[i]))return false;
			}
			return true;
		}
	}

	return a === b;
};

//사용하기
console.log( compare( [1,[2,3],5],[1,[2,3],5] ) );

재귀를 통해 간단히 해결되었습니다. 요소 각각은 for 를 통해 돌고 있으므로 중첩된 배열이 너무 깊지 않다면(100단계이상의 중첩) 큰 문제는 생기지 않겠죠.

오브젝트의 처리

마지막으로 남은 오브젝트도 큰 문제는 없지만 for in 을 통해 돌리는 경우 전체 요소의 수가 정확하게 일치하는지 확인할 방법이 없습니다. 따라서 카운터를 도입하여 b쪽의 키의 수를 세둔 뒤 a쪽과 일치검사를 할 때 하나씩 까나가는 방법으로 키의 갯수가 일치하는지 확인하면 됩니다.

var compare = function( a, b ){
	var type = typeof a, i, j;
	if( type == "object" ){
		if( a === null ) return a === b;
		else if( Array.isArray(a) ){
			if( !Array.isArray(b) || a.length != b.length ) return false;
			for( i = 0, j = a.length ; i < j ; i++ ){
				if(!compare(a[i], b[i]))return false;
			}
			return true;
		}else{ //일반 오브젝트인 경우

			//우선 b의 키 갯수를 세둔다.
			j = 0;
			for( i in b ){
				if( b.hasOwnProperty(i) ) j++;
			}

			//a의 각 키와 비교하면서 카운트를 제거해간다.
			for( i in a ){
				if( a.hasOwnProperty(i) ){
					if( !compare( a[i], b[i] ) ) return false;
					j--;
				}
			}

			//남은 카운트가 0이라면 같은 객체고 남아있다면 다른 객체임
			return !j;
		}
	}
	return a === b;
};

//사용하기
console.log( compare( {a:1,b:[2,3],c:5},{a:1, b:[2,3],c:5} ) );

결론

단순한 값의 비교에서 복잡한 구조를 가진 JSON 객체 간의 값이 일치하는지 검사할 때까지 두루두루 사용하기 편리합니다.
전체적으로 정리된 코드는 다음과 같습니다.

var compare = function(a, b){
	var i = 0, j;
	if(typeof a == "object" && a){
		if(Array.isArray(a)){
			if(!Array.isArray(b) || a.length != b.length) return false;
			for(j = a.length ; i < j ; i++) if(!compare(a[i], b[i])) return false;
			return true;
		}else{
			for(j in b) if(b.hasOwnProperty(j)) i++;
			for(j in a) if(a.hasOwnProperty(j)){
				if(!compare(a[j], b[j])) return false;
				i--;
			}
			return !i;
		}
	}
	return a === b;
};

테스트페이지는 아래와 같습니다.
http://jsfiddle.net/xq6mbx2n/1

스택버전 추가

  • 2016.05.22 – 간단히 스택버전을 추가합니다.
var compare = function(a, b){
	var stack = [[a,b]], curr, i;
	while(curr = stack.pop()){
		a = curr[0], b = curr[1];
		if((!a || typeof a != "object") && a !== b) return false;
		if(a instanceof Array){
			if(!(b instanceof Array) || (i = a.length) != b.length) return false;
			while(i--) stack.push([a[i], b[i]]);
		}else{
			if(Object.keys(a).length != Object.keys(b).length) return false;
			for(i in a) if(a.hasOwnProperty(i)) stack.push([a[i], b[i]]);
		}
	}
	return true;
};

es6와 이터레이션함수를 쓰면 좀 더 이쁠까요?

const compare = (a, b)=>{
	let stack = [[a,b]], i;
	for(let [A, B] of stack){
		if((!A || typeof A != "object") && A !== B) return false;
		if(A instanceof Array){
			if(!(A instanceof Array) || A.length != B.length) return false;
			A.forEach((v,i)=>stack.push([v, B[i]]));
		}else if((i = Object.keys(A)).length != Object.keys(B).length) return false;
		i.forEach(i=>stack.push([A[i], B[i]]));
	}
	return true;
};

약간 줄긴하는데 50보 100보군요 ^^

%d 블로거가 이것을 좋아합니다: