JacksonUtil 개발
This commit is contained in:
@@ -0,0 +1,261 @@
|
||||
package com.eactive.eai.common.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||
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;
|
||||
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
/**
|
||||
* Jackson JsonNode 범용 접근 유틸리티
|
||||
*
|
||||
* "." 구분자로 필드 단계를 이동하고, "[n]" 으로 배열 인덱스에 접근하는
|
||||
* path 표현식을 지원한다.
|
||||
*
|
||||
* 사용 예:
|
||||
* JacksonUtil.getText(root, "term_agreements[0].is_agreed")
|
||||
* JacksonUtil.getBoolean(root, "term_agreements[0].is_agreed", false)
|
||||
* JacksonUtil.getNode(root, "data.list[2].child[0].name")
|
||||
*
|
||||
* 경로 중간에 필드가 없거나, 배열 인덱스가 범위를 벗어나거나,
|
||||
* 배열이 아닌 노드에 인덱스 접근을 시도하는 경우 모두
|
||||
* 예외를 던지지 않고 null / 기본값을 반환한다.
|
||||
*/
|
||||
public final class JacksonUtil {
|
||||
|
||||
/** "fieldName" 또는 "fieldName[0]" 형태의 토큰을 분해하는 패턴 */
|
||||
private static final Pattern TOKEN_PATTERN = Pattern.compile("([^\\[\\]]*)((?:\\[\\d+\\])*)");
|
||||
private static final Pattern INDEX_PATTERN = Pattern.compile("\\[(\\d+)\\]");
|
||||
|
||||
private JacksonUtil() {
|
||||
// 인스턴스화 방지
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// 기본 트리 탐색
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Object(JSONObject/String/JsonNode 등)를 JsonNode로 변환한다.
|
||||
* KakaopayFilter.readTree()를 일반화한 버전.
|
||||
*/
|
||||
public static JsonNode readTree(Object jsonData, ObjectMapper objectMapper)
|
||||
throws JsonMappingException, JsonProcessingException {
|
||||
|
||||
if (jsonData == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (jsonData instanceof JsonNode) {
|
||||
return (JsonNode) jsonData;
|
||||
}
|
||||
|
||||
String jsonStr = null;
|
||||
|
||||
if (jsonData instanceof JSONObject) {
|
||||
jsonStr = ((JSONObject) jsonData).toJSONString();
|
||||
} else if (jsonData instanceof String) {
|
||||
jsonStr = (String) jsonData;
|
||||
} else {
|
||||
// 그 외 POJO 등은 writeValueAsString을 통해 변환
|
||||
jsonStr = objectMapper.writeValueAsString(jsonData);
|
||||
}
|
||||
|
||||
if (jsonStr == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return objectMapper.readTree(jsonStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* path 표현식으로 JsonNode를 탐색한다.
|
||||
* 경로가 존재하지 않으면 null을 반환한다 (MissingNode가 아닌 진짜 null).
|
||||
*
|
||||
* @param root 탐색을 시작할 JsonNode
|
||||
* @param path 예: "term_agreements[0].is_agreed", "data.list[2].name"
|
||||
*/
|
||||
public static JsonNode getNode(JsonNode root, String path) {
|
||||
if (root == null || path == null || path.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
JsonNode current = root;
|
||||
|
||||
for (String rawToken : path.split("\\.")) {
|
||||
if (current == null || current.isMissingNode() || current.isNull()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String fieldName = extractFieldName(rawToken);
|
||||
List<Integer> indices = extractIndices(rawToken);
|
||||
|
||||
// 필드명이 있으면 먼저 필드로 이동 (빈 문자열이면 현재 노드 유지 - 최상위 배열 접근용)
|
||||
if (!fieldName.isEmpty()) {
|
||||
if (!current.has(fieldName)) {
|
||||
return null;
|
||||
}
|
||||
current = current.get(fieldName);
|
||||
}
|
||||
|
||||
// 이어지는 [n][m]... 인덱스를 순서대로 적용
|
||||
for (Integer idx : indices) {
|
||||
if (current == null || !current.isArray() || idx < 0 || idx >= current.size()) {
|
||||
return null;
|
||||
}
|
||||
current = current.get(idx);
|
||||
}
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// 타입별 getter (전부 null-safe, 기본값 지원)
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
public static String getText(JsonNode root, String path) {
|
||||
return getText(root, path, null);
|
||||
}
|
||||
|
||||
public static String getText(JsonNode root, String path, String defaultValue) {
|
||||
JsonNode node = getNode(root, path);
|
||||
return (node == null || node.isMissingNode() || node.isNull()) ? defaultValue : node.asText(defaultValue);
|
||||
}
|
||||
|
||||
public static boolean getBoolean(JsonNode root, String path, boolean defaultValue) {
|
||||
JsonNode node = getNode(root, path);
|
||||
return (node == null || node.isMissingNode() || node.isNull()) ? defaultValue : node.asBoolean(defaultValue);
|
||||
}
|
||||
|
||||
public static int getInt(JsonNode root, String path, int defaultValue) {
|
||||
JsonNode node = getNode(root, path);
|
||||
return (node == null || node.isMissingNode() || node.isNull()) ? defaultValue : node.asInt(defaultValue);
|
||||
}
|
||||
|
||||
public static long getLong(JsonNode root, String path, long defaultValue) {
|
||||
JsonNode node = getNode(root, path);
|
||||
return (node == null || node.isMissingNode() || node.isNull()) ? defaultValue : node.asLong(defaultValue);
|
||||
}
|
||||
|
||||
public static double getDouble(JsonNode root, String path, double defaultValue) {
|
||||
JsonNode node = getNode(root, path);
|
||||
return (node == null || node.isMissingNode() || node.isNull()) ? defaultValue : node.asDouble(defaultValue);
|
||||
}
|
||||
|
||||
/** path가 가리키는 노드가 실제로 존재하는지 (null/missing이 아닌지) */
|
||||
public static boolean exists(JsonNode root, String path) {
|
||||
JsonNode node = getNode(root, path);
|
||||
return node != null && !node.isMissingNode() && !node.isNull();
|
||||
}
|
||||
|
||||
/** path가 가리키는 노드가 배열일 때 그 크기를 반환, 배열이 아니거나 없으면 -1 */
|
||||
public static int size(JsonNode root, String path) {
|
||||
JsonNode node = getNode(root, path);
|
||||
return (node != null && node.isArray()) ? node.size() : -1;
|
||||
}
|
||||
|
||||
/** path가 가리키는 ArrayNode를 List<JsonNode>로 반환, 없으면 빈 리스트 */
|
||||
public static List<JsonNode> getList(JsonNode root, String path) {
|
||||
List<JsonNode> result = new ArrayList<>();
|
||||
JsonNode node = getNode(root, path);
|
||||
if (node != null && node.isArray()) {
|
||||
for (JsonNode item : node) {
|
||||
result.add(item);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// 값 설정 (필요 시 사용 - 존재하는 경로에 대해서만 동작)
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* path가 가리키는 위치의 텍스트 값을 변경한다.
|
||||
* 부모 컨테이너(ObjectNode/ArrayNode)가 존재해야 하며, 중간 경로가 없으면 false를 반환한다.
|
||||
* (자동으로 중간 경로를 생성하지는 않음)
|
||||
*/
|
||||
public static boolean setText(JsonNode root, String path, String value) {
|
||||
return setValue(root, path, value);
|
||||
}
|
||||
|
||||
private static boolean setValue(JsonNode root, String path, String value) {
|
||||
int lastDot = path.lastIndexOf('.');
|
||||
String parentPath = (lastDot == -1) ? "" : path.substring(0, lastDot);
|
||||
String lastToken = (lastDot == -1) ? path : path.substring(lastDot + 1);
|
||||
|
||||
JsonNode parent = parentPath.isEmpty() ? root : getNode(root, parentPath);
|
||||
if (parent == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String fieldName = extractFieldName(lastToken);
|
||||
List<Integer> indices = extractIndices(lastToken);
|
||||
|
||||
JsonNode target = parent;
|
||||
if (!fieldName.isEmpty()) {
|
||||
if (!target.has(fieldName)) {
|
||||
return false;
|
||||
}
|
||||
target = target.get(fieldName);
|
||||
}
|
||||
|
||||
// 마지막 인덱스 전까지 이동
|
||||
for (int i = 0; i < indices.size() - 1; i++) {
|
||||
int idx = indices.get(i);
|
||||
if (target == null || !target.isArray() || idx < 0 || idx >= target.size()) {
|
||||
return false;
|
||||
}
|
||||
target = target.get(idx);
|
||||
}
|
||||
|
||||
if (!indices.isEmpty()) {
|
||||
// 배열의 특정 인덱스 값 교체
|
||||
int lastIdx = indices.get(indices.size() - 1);
|
||||
if (target == null || !target.isArray() || lastIdx < 0 || lastIdx >= target.size()) {
|
||||
return false;
|
||||
}
|
||||
((ArrayNode) target).set(lastIdx, value);
|
||||
return true;
|
||||
} else {
|
||||
// 객체 필드 값 교체 (target은 fieldName으로 이미 이동된 상태이므로, parent 기준 재설정 필요)
|
||||
if (parent.isObject() && fieldName != null && !fieldName.isEmpty()) {
|
||||
((ObjectNode) parent).put(fieldName, value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// 내부 파싱 헬퍼
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
/** "term_agreements[0]" -> "term_agreements" / "[0]" -> "" */
|
||||
private static String extractFieldName(String token) {
|
||||
Matcher m = TOKEN_PATTERN.matcher(token);
|
||||
if (m.matches()) {
|
||||
return m.group(1) == null ? "" : m.group(1);
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
/** "term_agreements[0][1]" -> [0, 1] / "term_agreements" -> [] */
|
||||
private static List<Integer> extractIndices(String token) {
|
||||
List<Integer> indices = new ArrayList<>();
|
||||
Matcher m = INDEX_PATTERN.matcher(token);
|
||||
while (m.find()) {
|
||||
indices.add(Integer.parseInt(m.group(1)));
|
||||
}
|
||||
return indices;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user