Algorithm

2개의 json key 값을 비교하는 방법

태인킴 2023. 6. 10. 14:54
반응형

2개의 json key 값을 비교하는 방법
2개의 json key 값을 비교하는 방법


요즘 다니는 회사는 산업용 스마트폰을 직접 제조하는 회사를 다니고 있습니다. 산업용 스마트폰이기 때문에 미국의 아마존과 같은 대형 공장에서 쓰이는 스마트폰을 제조하는데요. 이곳에서 저는 EMM(Enterprise Mobility Management)을 담당하는 팀에 소속해 있습니다. 공장 직원들이 사용하는 업무용 스마트폰이기 때문에 공장에서 원하는 스펙과 기능들을 충족 시켜주는 안드로이드 소프트웨어 개발로 지원하고 있지요. 

 

그 기능들 중에서 프로비저닝 이라는 기능이 있습니다. 제가 설명하는 내용보다 조금 더 넓은 개념으로 쓰이는데요. 제가 사용하는 '프로비저닝'은 안드로이드 스마트폰의 설정한 설정값들을 공장 직원들 업무용 폰에도 똑같이 적용해 주는 것이 '프로비저닝'입니다.  

 

 

1. 요구사항

'프로비저닝' 기능을 구현할때 주로 json 파일 형태로 설정값들을 저장해 두고 사용합니다. 그런데 공장에서 프로비저닝을 담당하는 IT 관리자가 이 json 파일을 직접 수정하는 경우가 대부분입니다. 사람이 직접 수정을 하기 때문에 json 파일에 오타가 들어가, 프로비저닝이 제대로 되지 않는 경우가 많습니다.

 

따라서 이러한 요구사항이 요청 되었습니다. json 파일의 존재하지 않는 key 값과 유효하지 않는 value 값을 프로비저닝 단계에서 알려달라는 요구사항이었습니다. 그리고 유효하지 않는 key, value 값이 있다면, 해당 값이 무엇인지 알려달라는 요구사항이었습니다. 

 

 

2. 알고리즘

알고리즘에 대해서 고민해 보겠습니다. 먼저 비교적 쉬운 value 값에 대한 유효성 검사는

  1. 각 value에 대한 정규표현식을 프로비저닝을 수행하는 어플리케이션에 정의해 놓고
  2. 프로비저닝 수행 시, 각각에 정규표현식을 모두 검사

 

key 값에 대한 유효성 검사는 조금 더 고민을 해봐야 합니다. key 값은 이름이 중복될 수 있습니다.

예를 들어,

{
    "wifi" : {
    	"name" : "123"
    },
    "bluetooth" : {
    	"name" : "234"
    }
}

 위와 같이, 'name'이라는 key 값은 중복이 가능합니다. 하지만, 두 'name'은 다른 'name'입니다. 두 'name'은 이렇게 표현 가능합니다. 'wifi/name', 'bluetooth/name' 이렇게 표현이 가능합니다. 이런 형태로 key을 저장해두고 있어야 비교하는 데 있어서 오류가 없습니다. 이제 알고리즘을 고민해 보겠습니다.

  1. 대조군 : key에 대한 유효한 값들을 모두 정의해 둔 집합
  2. 실험군 : 사용자가 설정한 json 파일에 key 값 집합
  3. 대조군, 실험군을 비교하여 실험군에만 존재하는 키값을 찾아낸다면, 사용자가 키값을 잘못 입력한 경우에 해당합니다.
  4. 근데, 이때 키값을 어떤 형태로 저장하는지가 중요합니다. 위와 같이, 'name'이라고 저장한다면, 사용자가 'sound/name' 값이라는 오타를, 성공한 케이스라고 처리합니다. 따라서, 저장하는 형태는 대조군, 실험군 모두 'wifi/name'과 같은 형태로 저장해야 합니다. 만약에 json이 아래와 같으면 어떻게 표현해야 할까요?
{
    "wifi" : {
    	"name" : [
        	"type" : "123"
        ]
    }
}

 이와 같은 형태는 'wifi/name/0/type' → '123' 이런 식으로 표현 가능합니다. 배열의 경우 인덱스를 넣어주어 표현 가능합니다. 그럼 종합하여, 알고리즘을 구체적으로 적어보겠습니다.

  1. 대조군 : 유효한 key 값 집합
  2. 실험군 : 사용자가 정의한 key 값 집합
  3. 대조군, 실험군에서 필요한 자료 구조를 만들기 위해서, 기존에 정의해 둔 java pojo를 모두 탐색하며, 'wifi/name/0/type' 이와 같은 형태에 데이터를 만들어줍니다. java pojo와 json 모두 tree 구조로 표현 가능합니다. 이런 tree구조를 탐색하는 방법에는 대표적으로 BFS(Breadth First Search), DFS(Depth First Search) 탐색 알고리즘이 있습니다. 더 편한 DFS를 이용하여 탐색하겠습니다.
  4. 대조군과 실험군을 비교하여, 실험군에만 존재하는 key 값을 찾습니다.

▶ DFS(깊이 우선 탐색) 알고리즘은 여기를 참고해 주세요.

▶ BFS(너비 우선 탐색) 알고리즘은 여기를 참고해 주세요.

 

DFS (깊이 우선 탐색)

* 탐색 그래프의 모든 노드들을 방문하는 일 ​ * 대표적인 두 가지 방법 1. BFS : Breadth First Search, 너비 우선 탐색 2. DFS : Depth First Search, 깊이 우선 탐색 ​ * DFS 맹목적 탐색 방법의 하나로 출발 노

coding-food-court.tistory.com

 

그러면 여기서 핵심이 되는 'wifi/name/0/type' 형태에 자료구조를 만들어보겠습니다. java 인스턴스는 Map<String, Object> 형태로 표현 가능합니다. 

public LinkedHashMap<String, Object> search(Map<String, Object> map) {
    LinkedHashMap<String, Object> result = new LinkedHashMap<>();
    if (map == null) return result;
    for (Entry entry : map.entrySet()) {
    	search(result, "/" + entry.getKey(), entry.getValue());
    }
    return result;
}

public Map<String, Object> search(Map<String, Object> result, String key, Object value) {
    if (value == null) return result;
    
    if (value instanceOf List) {
    	result.put(key, value);
        List subList = (List)value;
        int i = 0;
        for (Object ele : subList) {
            search(result, key + "/" + i, ele);
            i++;
        }
    } else if (value instanceOf Map) {
    	result.put(key, value);
        Map subMap = (Map)value;
        for (Entry ele : subMap.entrySet()) {
        	search(result, key + "/" + ele.getKey(), ele.getValue());
        }
    } else {
    	result.put(key, value);
    }
    return result;
}

 위와 같이 DFS 알고리즘을 구성할 수 있습니다. 두번째 search 메소드 코드를 보면, base case를 다음과 같이 구성할수 있습니다.

  1. value == null
  2. value instanceOf String, Integer, Double, Float, Boolean

두 base case가 탈출 조건입니다. 나머지 조건인 value가 List, Map 형태일 때는 tree의 근접 노드들을 더 탐색해야 합니다. 따라서 stack 구조인 재귀함수를 다시 호출하도록 구현하였습니다.

 

 

3. 마치며

위와 같은 코드로 저는 json 또는 java pojo로부터  'wifi/name/0/type' 형태의 구조를 만들 수 있었습니다. 나머지 key 검사 코드와 value 검사 코드를 포함하여, 아래 github에 올려두었습니다.

▶ github

반응형