본문 바로가기

카테고리 없음

Jackson에서 DFS - JSON 탐색

728x90

Json처리 할 때 DFS가 필요한 이유

Java에서 Json형식을 다룰 일은 많습니다. post호출의 RequestBody를 Json형태로 보내는 경우도 많고 웹에서도 거의 표준으로 많이 쓰기 때문입니다. 그런데 이 Json형식의 데이터에서 특정 조건의 값을 검색해서 처리해야 하는 경우에 DFS알고리즘이 필요 합니다.

 

문제

아래 json형식의 데이터에서 값이 null인 key는 "textbook"입니다. 눈으로 보면 찾을 수 있겠지만 json이 복잡하거나 어플리케이션에서 판단을 해야 한다고 하면 난감할 수 있을 것입니다. steps같은 경우는 Array형태 인데 이 Array형태의 값들도 모두 조사를 해야 하기 때문입니다.

 

hello.json

{
  "productType": 3,
  "scriptType": "RB003",
  "standardScript": {
    "id": "P1",
    "name": "Kim"
  },
  "steps": [
    {
      "id": "S1",
      "name": "Anna"
    },
    {
      "id": "S2",
      "name": "Brian"
    }
  ],
  "textbook": null
}

 

Jackson에서 재귀를 태울지 결정하는 부분

Jackson은 ContainerNode와 ContainerNode가 아닌 Node로 구분이 됩니다.

ContainerNode ContainerNode가 아닌 Node
ArrayNode - [] IntNode - [0-9]
ObjectNode - {} TextNode - [a-z] | [가-힣]

여기에서 한 Depth들어갈 수 있는 Node는 ContainerNode뿐입니다.

 

아래 DFS알고리즘을 이용 하면 모든 노드를 돌면서 null인 값을 찾을 수 있습니다.

public class DFSJsonNode {
    public static JsonNode searchForEntity(JsonNode node) {
        if (node == null) {
            return null;
        }
        if (!node.isContainerNode()) {
            return null;
        }
        for (JsonNode child : node) {
            if (child.isContainerNode()) {
                System.out.printf("%s %s \n",child.getClass().getSimpleName(), child);
                JsonNode childResult = searchForEntity(child);
                if (childResult != null && !childResult.isMissingNode()) {
                    return childResult;
                }
            }else {
                System.out.printf("--Text,Int Node--%s %s \n",child.getClass().getSimpleName(), child);
            }
        }
        return null;
    }

    public static void main(String[] args) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        JsonNode jsonNode = mapper.readTree(new File("./hello.json"));
        System.out.println(jsonNode);
        JsonNode i = searchForEntity(jsonNode);
    }
}

 

결과

-----Text,Int----IntNode 3 
-----Text,Int----TextNode "RB003" 
-----Text,Int----TextNode "P1" 
-----Text,Int----TextNode "Kim" 
-----Text,Int----TextNode "S1" 
-----Text,Int----TextNode "Anna" 
-----Text,Int----TextNode "S2" 
-----Text,Int----TextNode "Brian" 
-----Text,Int----NullNode null 
null

결과 해석

ContainerNode가 아닐때 System.out을 찍었기 때문에 TextNode와 IntNode만 출력 됩니다.

 

위 로직에서 한 Depth더 들어가서 검색 하려면 재귀 호출을 해야 하는데 재귀 호출을 하는 기준이 ContainerNode면 재귀 호출을 하고 아니면 null 처리를 합니다.

 

위와 같이 돌리면 전체 순회를 하고 끝나게 됩니다.

 

재귀를 태울 노드들

그러면 재귀를 태울 노드는 어떻게 표현이 될까요?

public static JsonNode searchForEntity(JsonNode node) {
        if (node == null) {
            return null;
        }
        if (node.has(null)) {
            return node.get(null);
        }
        if (!node.isContainerNode()) {
            return null;
        }
        for (JsonNode child : node) {
            if (child.isContainerNode()) {
                System.out.printf("%s %s \n",child.getClass().getSimpleName(), child);
                JsonNode childResult = searchForEntity(child);
                if (childResult != null && !childResult.isMissingNode()) {
                    return childResult;
                }
            }
        }
        return null;
}

 

결과

ObjectNode {"id":"P1","name":"Kim"} 
ArrayNode [{"id":"S1","name":"Anna"},{"id":"S2","name":"Brian"}] 
ObjectNode {"id":"S1","name":"Anna"} 
ObjectNode {"id":"S2","name":"Brian"} 
null

 

결과 해석

child.isContainerNode()일 때 System.out을 찍었기 때문에 ContainerNode들만 나왔습니다. child.isContainerNode()일 때 재귀 searchForEntity를 타도록 되어 있습니다.

 

728x90
블로그 주인장입니다. 원하시는 정보는 얻으셨나요? 이 포스트에서 추가로 필요한 정보가 있으시면 여기에 남겨주세요.