Files
elink-online-common/src/main/java/com/eactive/eai/util/JsonPathUtil.java
T
2026-06-30 16:24:46 +09:00

224 lines
9.2 KiB
Java

package com.eactive.eai.util;
import org.json.simple.JSONObject;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
// TOUSE
// kbbank 어댑터에서 업무데이터의 암호화 필드에 대한 암복호화시
// 해당 필드에 대한 get/set 을 수행
public class JsonPathUtil {
private JsonPathUtil() {
}
private static final ObjectMapper objectMapper = new ObjectMapper();
// ---------------------------------------------------------------
// JsonNode 기반 단일 파싱 API — 연속 get/set 시 파싱 횟수 절감
// ---------------------------------------------------------------
/** JSON 문자열을 JsonNode 트리로 파싱. */
public static JsonNode toTree(Object json) throws Exception {
if(json == null)
return null;
else if(json instanceof JsonNode)
return (JsonNode)json;
else if(json instanceof String)
return objectMapper.readTree((String)json);
else if(json instanceof JSONObject)
return objectMapper.readTree(((JSONObject)json).toJSONString());
else
return objectMapper.readTree(json.toString());
}
/** 이미 파싱된 트리에서 경로의 값을 추출. */
public static String getAt(JsonNode root, String path) {
JsonNode node = getNodeAtPath(root, path, "/");
return node != null ? node.asText() : null;
}
/** 이미 파싱된 트리에서 경로의 값을 추출. */
public static String getAt(JsonNode root, String path, String delimeter) {
JsonNode node = getNodeAtPath(root, path, delimeter);
return node != null ? node.asText() : null;
}
/** 이미 파싱된 트리의 경로에 값을 설정 (in-place). */
public static void setAt(JsonNode root, String path, String value) {
JsonNode parent = getParentNode(root, getParentPath(path));
if (parent == null) return;
String key = getFieldName(path);
if (parent.isArray()) {
((ArrayNode) parent).set(Integer.parseInt(key), objectMapper.convertValue(value, JsonNode.class));
} else if (parent.isObject()) {
((ObjectNode) parent).put(key, value);
}
}
/** JsonNode 트리를 JSON 문자열로 직렬화. */
public static String fromTree(JsonNode root) throws Exception {
return objectMapper.writeValueAsString(root);
}
// ---------------------------------------------------------------
// 문자열 기반 API (내부적으로 위 메서드에 위임)
// ---------------------------------------------------------------
// 특정 경로의 값을 설정하는 함수
public static String setValueAtPath(String jsonString, String path, String newValue, boolean isPretty) {
try {
JsonNode rootNode = objectMapper.readTree(jsonString);
setAt(rootNode, path, newValue);
if(isPretty)
return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(rootNode);
else
return objectMapper.writeValueAsString(rootNode);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
// 특정 경로의 값을 가져오는 함수
public static String getValueAtPath(String jsonString, String path) {
try {
return getAt(objectMapper.readTree(jsonString), path);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
// 경로에서 부모 경로를 추출하는 유틸리티 함수
private static String getParentPath(String path) {
int lastSlashIndex = path.lastIndexOf('/');
return lastSlashIndex == 0 ? "/" : path.substring(0, lastSlashIndex);
}
// 경로에서 필드 이름 또는 배열 인덱스를 추출하는 유틸리티 함수
private static String getFieldName(String path) {
int lastSlashIndex = path.lastIndexOf('/');
return path.substring(lastSlashIndex + 1);
}
// 특정 경로에 해당하는 부모 노드를 가져오는 유틸리티 함수
private static JsonNode getParentNode(JsonNode rootNode, String parentPath) {
return getNodeAtPath(rootNode, parentPath, "/");
}
// 특정 경로에 해당하는 노드를 가져오는 유틸리티 함수
private static JsonNode getNodeAtPath(JsonNode rootNode, String path, String delimeter) {
String[] tokens = path.split(delimeter);
JsonNode currentNode = rootNode;
for (String token : tokens) {
if (token.isEmpty()) continue;
if (token.matches("\\d+")) {
// 배열 인덱스 처리
currentNode = currentNode.get(Integer.parseInt(token));
} else {
// 객체 필드 처리
currentNode = currentNode.get(token);
}
if (currentNode == null) {
return null; // 경로가 존재하지 않음
}
}
return currentNode;
}
/**
* 이미 파싱된 bodyNode에서 removeFromPath 필드를 제거하고 mergeJson을 병합.
* body의 재파싱 없이 호출 가능하여 파싱 횟수를 1회 절감.
*
* body와 mergeJson이 모두 JSON 객체인 경우에만 병합을 수행한다.
* 어느 한쪽이 JSON 객체가 아니면 mergeJson을 그대로 반환한다 (전체 교체 fallback).
*
* @param bodyNode 이미 파싱된 원본 JsonNode
* @param removeFromPath 제거할 필드의 경로 (예: /encryptedData)
* @param mergeJson 병합할 JSON 문자열
* @return 병합된 JSON 문자열, 병합 불가 시 mergeJson 그대로 반환
*/
public static JsonNode mergeAtRoot(JsonNode bodyNode, String removeFromPath, String mergeJson) {
try {
JsonNode mergeNode = objectMapper.readTree(mergeJson);
if (!bodyNode.isObject() || !mergeNode.isObject()) {
return bodyNode;
}
ObjectNode result = (ObjectNode) bodyNode.deepCopy();
// removeFromPath 필드 제거
int lastSlash = removeFromPath.lastIndexOf('/');
String fieldName = removeFromPath.substring(lastSlash + 1);
if (lastSlash == 0) {
result.remove(fieldName);
} else {
JsonNode parent = getNodeAtPath(result, removeFromPath.substring(0, lastSlash), "/");
if (parent instanceof ObjectNode) {
((ObjectNode) parent).remove(fieldName);
}
}
// mergeJson 필드를 body에 병합 (기존 필드 덮어쓰기)
result.setAll((ObjectNode) mergeNode);
// return objectMapper.writeValueAsString(result);
return result;
} catch (Exception e) {
return bodyNode;
}
}
/**
* body에서 removeFromPath 필드를 제거한 뒤, mergeJson의 필드를 body에 병합하여 반환한다.
* (문자열 기반 overload — 내부적으로 body를 1회 파싱하여 위 메서드에 위임)
*
* 사용 예 (toPath="/" 복호화):
* body = {"encryptedData":"...","requestId":"REQ001"}
* removeFrom = /encryptedData
* mergeJson = {"userId":"U001","name":"test"}
* 결과 = {"requestId":"REQ001","userId":"U001","name":"test"}
*
* @param body 원본 JSON 문자열
* @param removeFromPath 제거할 필드의 경로 (예: /encryptedData)
* @param mergeJson 병합할 JSON 문자열
* @return 병합된 JSON 문자열, 병합 불가 시 mergeJson 그대로 반환
*/
public static String mergeAtRoot(String body, String removeFromPath, String mergeJson) {
try {
return objectMapper.writeValueAsString(mergeAtRoot(objectMapper.readTree(body), removeFromPath, mergeJson));
} catch (Exception e) {
return mergeJson;
}
}
public static void main(String[] args) {
String jsonString = "{ \"person\": { \"name\": \"John\", \"age\": 30, \"groups\": [ { \"id\": 1, \"name\": \"Group A\" }, { \"id\": 2, \"name\": \"Group B\" }, { \"id\": 3, \"name\": \"Group C\" } ] } }";
// System.out.println("Source JSON: \n" + jsonString);
// 경로를 사용하여 값 가져오기 (반복되는 그룹 포함)
String getPath = "/person/groups/1/name";
String secondGroupName = getValueAtPath(jsonString, getPath);
// System.out.println("getPath: " + getPath + " -> " + secondGroupName);
// 경로를 사용하여 값 설정하기 (반복되는 그룹 포함) 및 Pretty 출력
String putPath = "/person/groups/2/name";
String updatedJsonString = setValueAtPath(jsonString, putPath, "Group Z", true);
// System.out.println("putPath: " + putPath);
// System.out.println("Updated JSON (Pretty): \n" + updatedJsonString);
getPath = "/person/name";
// System.out.println("getPath: " + getPath + " -> " + getValueAtPath(jsonString, getPath));
getPath = "/person/age";
// System.out.println("getPath: " + getPath + " -> " + getValueAtPath(jsonString, getPath));
}
}