224 lines
9.2 KiB
Java
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));
|
|
}
|
|
}
|