JSON 경로가 / 없이 와도 필드 제거·설정이 되도록 수정
- mergeAtRoot / setAt 에서 "encrypted_data" 처럼 선행 / 가 없는 경로를 "/encrypted_data" 로 맞춤 (substring(0, -1) 예외로 병합이 조용히 실패하던 문제) - mergeAtRoot 실패 시 원본을 반환하던 catch 에 WARN 로그 추가
This commit is contained in:
@@ -3,6 +3,7 @@ package com.eactive.eai.util;
|
|||||||
import org.json.simple.JSONObject;
|
import org.json.simple.JSONObject;
|
||||||
|
|
||||||
import com.eactive.eai.common.util.JacksonUtil;
|
import com.eactive.eai.common.util.JacksonUtil;
|
||||||
|
import com.eactive.eai.common.util.Logger;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||||
@@ -21,6 +22,19 @@ public class JsonPathUtil {
|
|||||||
// 기본 ObjectMapper 는 100000000.00 을 1.0E8 로 바꿔버린다.
|
// 기본 ObjectMapper 는 100000000.00 을 1.0E8 로 바꿔버린다.
|
||||||
private static final ObjectMapper objectMapper = JacksonUtil.newNumberSafeMapper();
|
private static final ObjectMapper objectMapper = JacksonUtil.newNumberSafeMapper();
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 경로를 "/" 로 시작하도록 맞춘다. ("encrypted_data" → "/encrypted_data")
|
||||||
|
* 부모 경로·필드명 계산이 선행 "/" 를 전제로 하므로, 없으면 substring(0, -1) 로 실패한다.
|
||||||
|
*/
|
||||||
|
private static String normalizePath(String path) {
|
||||||
|
if (path == null || path.startsWith("/")) {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
return "/" + path;
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
// JsonNode 기반 단일 파싱 API — 연속 get/set 시 파싱 횟수 절감
|
// JsonNode 기반 단일 파싱 API — 연속 get/set 시 파싱 횟수 절감
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
@@ -53,6 +67,7 @@ public class JsonPathUtil {
|
|||||||
|
|
||||||
/** 이미 파싱된 트리의 경로에 값을 설정 (in-place). */
|
/** 이미 파싱된 트리의 경로에 값을 설정 (in-place). */
|
||||||
public static void setAt(JsonNode root, String path, String value) {
|
public static void setAt(JsonNode root, String path, String value) {
|
||||||
|
path = normalizePath(path);
|
||||||
JsonNode parent = getParentNode(root, getParentPath(path));
|
JsonNode parent = getParentNode(root, getParentPath(path));
|
||||||
if (parent == null) return;
|
if (parent == null) return;
|
||||||
String key = getFieldName(path);
|
String key = getFieldName(path);
|
||||||
@@ -159,7 +174,8 @@ public class JsonPathUtil {
|
|||||||
|
|
||||||
ObjectNode result = (ObjectNode) bodyNode.deepCopy();
|
ObjectNode result = (ObjectNode) bodyNode.deepCopy();
|
||||||
|
|
||||||
// removeFromPath 필드 제거
|
// removeFromPath 필드 제거 ("encrypted_data" 처럼 "/" 없이 와도 같은 경로로 본다)
|
||||||
|
removeFromPath = normalizePath(removeFromPath);
|
||||||
int lastSlash = removeFromPath.lastIndexOf('/');
|
int lastSlash = removeFromPath.lastIndexOf('/');
|
||||||
String fieldName = removeFromPath.substring(lastSlash + 1);
|
String fieldName = removeFromPath.substring(lastSlash + 1);
|
||||||
if (lastSlash == 0) {
|
if (lastSlash == 0) {
|
||||||
@@ -176,6 +192,8 @@ public class JsonPathUtil {
|
|||||||
// return objectMapper.writeValueAsString(result);
|
// return objectMapper.writeValueAsString(result);
|
||||||
return result;
|
return result;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
// 병합 실패 시 원본을 그대로 돌려주므로, 원인이 묻히지 않게 남긴다.
|
||||||
|
logger.warn("JsonPathUtil] mergeAtRoot failed. return original body. removeFromPath=" + removeFromPath, e);
|
||||||
return bodyNode;
|
return bodyNode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -199,6 +217,7 @@ public class JsonPathUtil {
|
|||||||
try {
|
try {
|
||||||
return objectMapper.writeValueAsString(mergeAtRoot(objectMapper.readTree(body), removeFromPath, mergeJson));
|
return objectMapper.writeValueAsString(mergeAtRoot(objectMapper.readTree(body), removeFromPath, mergeJson));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.warn("JsonPathUtil] mergeAtRoot failed. return mergeJson. removeFromPath=" + removeFromPath, e);
|
||||||
return mergeJson;
|
return mergeJson;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
package com.eactive.eai.util;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JsonPathUtil 단위테스트
|
||||||
|
*
|
||||||
|
* 경로를 "/" 없이 넘겨도("encrypted_data") "/encrypted_data" 와 같게 동작하는지와,
|
||||||
|
* 기존 "/" 경로·배열 인덱스 동작이 유지되는지를 본다.
|
||||||
|
*/
|
||||||
|
class JsonPathUtilTest {
|
||||||
|
|
||||||
|
private static final String ENC_BODY = "{\"encrypted_data\":\"CIPHER\",\"requestId\":\"R1\"}";
|
||||||
|
private static final String PLAIN = "{\"userId\":\"U1\",\"name\":\"test\"}";
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("1. mergeAtRoot - 경로 앞에 / 가 없어도 암호문 필드를 지우고 병합한다")
|
||||||
|
void mergeAtRoot_슬래시없음() throws Exception {
|
||||||
|
JsonNode withSlash = JsonPathUtil.mergeAtRoot(JsonPathUtil.toTree(ENC_BODY), "/encrypted_data", PLAIN);
|
||||||
|
JsonNode withoutSlash = JsonPathUtil.mergeAtRoot(JsonPathUtil.toTree(ENC_BODY), "encrypted_data", PLAIN);
|
||||||
|
|
||||||
|
assertEquals(withSlash, withoutSlash);
|
||||||
|
assertFalse(withoutSlash.has("encrypted_data"), "암호문 필드는 제거돼야 한다");
|
||||||
|
assertEquals("R1", withoutSlash.get("requestId").asText());
|
||||||
|
assertEquals("U1", withoutSlash.get("userId").asText());
|
||||||
|
assertEquals("test", withoutSlash.get("name").asText());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("2. mergeAtRoot - 중첩 경로도 / 유무와 관계없이 해당 필드를 지운다")
|
||||||
|
void mergeAtRoot_중첩경로() throws Exception {
|
||||||
|
String body = "{\"data\":{\"encrypted_data\":\"CIPHER\",\"k\":\"v\"},\"r\":\"1\"}";
|
||||||
|
|
||||||
|
JsonNode withSlash = JsonPathUtil.mergeAtRoot(JsonPathUtil.toTree(body), "/data/encrypted_data", PLAIN);
|
||||||
|
JsonNode withoutSlash = JsonPathUtil.mergeAtRoot(JsonPathUtil.toTree(body), "data/encrypted_data", PLAIN);
|
||||||
|
|
||||||
|
assertEquals(withSlash, withoutSlash);
|
||||||
|
assertFalse(withoutSlash.get("data").has("encrypted_data"));
|
||||||
|
assertEquals("v", withoutSlash.get("data").get("k").asText());
|
||||||
|
assertEquals("U1", withoutSlash.get("userId").asText());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("3. mergeAtRoot - 병합할 값이 JSON 객체가 아니면 원본을 그대로 반환한다")
|
||||||
|
void mergeAtRoot_객체아님() throws Exception {
|
||||||
|
JsonNode body = JsonPathUtil.toTree(ENC_BODY);
|
||||||
|
|
||||||
|
JsonNode result = JsonPathUtil.mergeAtRoot(body, "encrypted_data", "\"plain text\"");
|
||||||
|
|
||||||
|
assertSame(body, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("4. mergeAtRoot(String) - 문자열 오버로드도 / 없이 병합한다")
|
||||||
|
void mergeAtRoot_문자열() throws Exception {
|
||||||
|
String result = JsonPathUtil.mergeAtRoot(ENC_BODY, "encrypted_data", PLAIN);
|
||||||
|
|
||||||
|
JsonNode node = JsonPathUtil.toTree(result);
|
||||||
|
assertFalse(node.has("encrypted_data"));
|
||||||
|
assertEquals("U1", node.get("userId").asText());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("5. setAt - 경로 앞에 / 가 없어도 최상위·중첩 필드에 값을 설정한다")
|
||||||
|
void setAt_슬래시없음() throws Exception {
|
||||||
|
JsonNode root = JsonPathUtil.toTree("{\"data\":{\"value\":\"old\"}}");
|
||||||
|
|
||||||
|
JsonPathUtil.setAt(root, "encrypted_data", "CIPHER");
|
||||||
|
JsonPathUtil.setAt(root, "data/value", "new");
|
||||||
|
|
||||||
|
assertEquals("CIPHER", root.get("encrypted_data").asText());
|
||||||
|
assertEquals("new", root.get("data").get("value").asText());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("6. 기존 동작 유지 - / 경로와 배열 인덱스 경로의 조회·설정")
|
||||||
|
void 기존동작_유지() throws Exception {
|
||||||
|
JsonNode root = JsonPathUtil.toTree(
|
||||||
|
"{\"person\":{\"name\":\"John\",\"groups\":[{\"name\":\"A\"},{\"name\":\"B\"}]}}");
|
||||||
|
|
||||||
|
assertEquals("B", JsonPathUtil.getAt(root, "/person/groups/1/name"));
|
||||||
|
assertEquals("John", JsonPathUtil.getAt(root, "person/name"));
|
||||||
|
|
||||||
|
JsonPathUtil.setAt(root, "/person/groups/0/name", "Z");
|
||||||
|
JsonPathUtil.setAt(root, "/topLevel", "T");
|
||||||
|
|
||||||
|
assertEquals("Z", JsonPathUtil.getAt(root, "/person/groups/0/name"));
|
||||||
|
assertTrue(root.has("topLevel"));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user