newNumberSafeMapper 출력측 옵션 추가 및 ArrayNode.set 버전 호환 수정
JacksonUtil.newNumberSafeMapper() 는 USE_BIG_DECIMAL_FOR_FLOATS + withExactBigDecimals 로 입력(파싱)측만 막고 있었다. DecimalNode 직렬화는 BigDecimal.toString() 이라 scale 이 음수이거나 adjusted exponent < -6 일 때 지수 표기가 나온다. 즉 파싱을 제대로 해도 내보낼 때 작은 소수가 깨졌다. 0.00000012 -> 1.2E-7 , -0.0000005 -> -5E-7 WRITE_BIGDECIMAL_AS_PLAIN 을 추가해 출력측까지 보존한다. (금액처럼 scale >= 0 인 큰 값은 원래 영향 없음. 이율/환율이 대상) 또한 JacksonUtil.setValue() 가 ArrayNode.set(int, String) 을 쓰고 있었다. 이 오버로드는 jackson-databind 2.13 부터이고, 실제 런타임은 2.12.7 이다 (2.12.7 은 set(int, JsonNode) 하나뿐). jackson-dataformat-xml:2.13.1 이 compileOnly 로 컴파일 클래스패스에만 2.13.1 을 올려 컴파일만 통과하던 상태였고, 배열 경로에 setText() 하면 운영에서 NoSuchMethodError 가 났다. TextNode.valueOf() 로 감싸 set(int, JsonNode) 에 바인딩한다. 그 외 - TemplateCodeConvertAdapterErrorMsgHandler: 응답 JSON 왕복 지점 전환 - HttpAdapterServiceSupport, HttpClient5AdapterServiceRestAddFilter: 직렬화 지점 전환 - JsonReader: parse() 호출마다 ObjectMapper 를 새로 만들던 것을 static 싱글턴으로 바꾸고 중복 설정 코드를 JacksonUtil 에 위임 - MessageKeyExtractor: 선언만 하고 쓰지 않는 ObjectMapper 필드 제거 - build.gradle: 미사용 jackson-dataformat-xml 제거 (common 은 Ignite 경유로 jackson 확보) 검증: gradlew compileJava BUILD SUCCESSFUL, databind 2.12.7 런타임에서 숫자 왕복 4건 + setText 배열/객체/미존재 경로 5건 전부 통과.
This commit is contained in:
+3
-1
@@ -142,7 +142,9 @@ dependencies {
|
||||
compileOnly 'javax.resource:javax.resource-api:1.7'
|
||||
compileOnly 'javax.jms:javax.jms-api:2.0.1'
|
||||
|
||||
compileOnly group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-xml', version: '2.13.1'
|
||||
// jackson-dataformat-xml 제거 (2026-08-27)
|
||||
// XmlMapper/JacksonXml* 사용처가 전 소스에 0건이고, 선언 버전(2.13.1)이
|
||||
// 실제 해석되는 jackson-core/databind(2.12.7)와 마이너 불일치라 승격 시 위험했다.
|
||||
|
||||
api "org.springframework.security:spring-security-jwt:1.1.1.RELEASE"
|
||||
|
||||
|
||||
+3
-1
@@ -6,6 +6,7 @@ import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.eactive.eai.common.message.EAIMessage;
|
||||
import com.eactive.eai.common.property.PropManager;
|
||||
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.ObjectMapper;
|
||||
@@ -19,7 +20,8 @@ public class TemplateCodeConvertAdapterErrorMsgHandler extends TemplateAdapterEr
|
||||
/** PropManager 에서 코드 변환 설정을 조회할 프로퍼티 그룹 이름 */
|
||||
static final String PROP_GROUP = "AdapterErrorMessageHandler{CODE_CONVERT}";
|
||||
|
||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
// 기본 ObjectMapper 는 응답 JSON 왕복에서 100000000.00 을 1.0E8 로 바꿔버린다.
|
||||
private static final ObjectMapper OBJECT_MAPPER = JacksonUtil.newNumberSafeMapper();
|
||||
|
||||
@Override
|
||||
public Object generateNonStandardErrorResponseMessage(
|
||||
|
||||
+3
-1
@@ -9,6 +9,7 @@ import org.json.simple.JSONObject;
|
||||
import com.eactive.eai.adapter.http.client.HttpClientAdapterServiceKey;
|
||||
import com.eactive.eai.adapter.http.client.impl.filter.HttpClient5AdapterFilterFactory;
|
||||
import com.eactive.eai.adapter.http.client.impl.filter.HttpClientAdapterFilter;
|
||||
import com.eactive.eai.common.util.JacksonUtil;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
@@ -37,7 +38,8 @@ public class HttpClient5AdapterServiceRestAddFilter extends HttpClient5AdapterSe
|
||||
// // Adapter에서 Exception이 발생한 경우, 처리할 필터
|
||||
// static final String EXCEPTION_FILTER = "EXCEPTION_FILTER";
|
||||
|
||||
private ObjectMapper mapper = new ObjectMapper();
|
||||
// 기본 ObjectMapper 는 JsonNode 직렬화 시 작은 소수를 1.2E-7 로 바꿔버린다.
|
||||
private ObjectMapper mapper = JacksonUtil.newNumberSafeMapper();
|
||||
|
||||
/**
|
||||
* 1. 기능 : HttpClient 호출 전후 Filter 적용용 2. 처리 개요 : <br>
|
||||
|
||||
@@ -19,6 +19,7 @@ import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilterType;
|
||||
import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthException;
|
||||
import com.eactive.eai.common.TransactionContextKeys;
|
||||
import com.eactive.eai.common.server.EAIServerManager;
|
||||
import com.eactive.eai.common.util.JacksonUtil;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
import com.eactive.eai.common.util.TxSiftContext;
|
||||
import com.eactive.eai.common.util.UUIDGenerator;
|
||||
@@ -31,7 +32,8 @@ public abstract class HttpAdapterServiceSupport implements HttpAdapterService, H
|
||||
public static final String HEADER_NAME_CLIENT_ID = "x-elink-client-id";
|
||||
public static final String PROPERTIES_NAME_CLIENT_ID = "clientId";
|
||||
static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
|
||||
private ObjectMapper mapper = new ObjectMapper();
|
||||
// 기본 ObjectMapper 는 JsonNode 직렬화 시 작은 소수를 1.2E-7 로 바꿔버린다.
|
||||
private ObjectMapper mapper = JacksonUtil.newNumberSafeMapper();
|
||||
EAIServerManager eaiServerManager;
|
||||
String instid = null;
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||
@@ -13,6 +14,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.fasterxml.jackson.databind.node.TextNode;
|
||||
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
@@ -42,16 +44,26 @@ public final class JacksonUtil {
|
||||
/**
|
||||
* JSON 숫자를 double 로 좁히지 않고 BigDecimal 로, 수신한 자릿수 그대로 유지하는 ObjectMapper.
|
||||
*
|
||||
* 입력측(파싱) - 2개 옵션이 함께 필요하다.
|
||||
* readTree() 로 파싱한 뒤 writeValueAsString() 으로 다시 문자열을 만드는 왕복에서,
|
||||
* 기본 설정이면 100000000.00 이 1.0E8 로 변형된다.
|
||||
* USE_BIG_DECIMAL_FOR_FLOATS 만 켜고 withExactBigDecimals(true) 를 빼면 기본
|
||||
* JsonNodeFactory 가 stripTrailingZeros() 를 적용해 scale 이 음수가 되어 1E+8 이 된다.
|
||||
* 두 옵션을 함께 켜야 수신한 값이 그대로 보존된다.
|
||||
*
|
||||
* 출력측(직렬화) - WRITE_BIGDECIMAL_AS_PLAIN 이 추가로 필요하다.
|
||||
* DecimalNode 직렬화는 결국 BigDecimal.toString() 이고, 이것은
|
||||
* scale 이 음수이거나 adjusted exponent 가 -6 미만일 때 지수 표기를 쓴다.
|
||||
* 즉 위 2개 옵션으로 파싱을 제대로 해도, 내보낼 때 작은 소수가 깨진다.
|
||||
* 0.00000012 -> 1.2E-7 , -0.0000005 -> -5E-7
|
||||
* 금액처럼 scale 이 0 이상인 큰 값은 영향이 없지만, 이율/환율은 깨진다.
|
||||
* (Jackson 2.12.7 실측, 2026-08-27)
|
||||
*/
|
||||
public static ObjectMapper newNumberSafeMapper() {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
objectMapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);
|
||||
objectMapper.setNodeFactory(JsonNodeFactory.withExactBigDecimals(true));
|
||||
objectMapper.enable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN);
|
||||
return objectMapper;
|
||||
}
|
||||
|
||||
@@ -256,7 +268,9 @@ public final class JacksonUtil {
|
||||
if (target == null || !target.isArray() || lastIdx < 0 || lastIdx >= target.size()) {
|
||||
return false;
|
||||
}
|
||||
((ArrayNode) target).set(lastIdx, value);
|
||||
// set(int, String) 오버로드는 jackson-databind 2.13 부터다. 실제 런타임은 2.12.7 이므로
|
||||
// TextNode 로 감싸 set(int, JsonNode) 에 바인딩해야 NoSuchMethodError 가 나지 않는다.
|
||||
((ArrayNode) target).set(lastIdx, TextNode.valueOf(value));
|
||||
return true;
|
||||
} else {
|
||||
// 객체 필드 값 교체 (target은 fieldName으로 이미 이동된 상태이므로, parent 기준 재설정 필요)
|
||||
|
||||
@@ -9,7 +9,6 @@ import com.eactive.eai.common.messagekey.MessageKeyGroupVO;
|
||||
import com.eactive.eai.common.messagekey.MessageKeyManager;
|
||||
import com.eactive.eai.common.messagekey.MessageKeyVO;
|
||||
import com.eactive.eai.transformer.message.ISO8583MessageFactory;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.solab.iso8583.IsoMessage;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.json.simple.JSONValue;
|
||||
@@ -23,8 +22,6 @@ public final class MessageKeyExtractor {
|
||||
|
||||
static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
|
||||
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
/**
|
||||
* Private 생성자
|
||||
* Instance를 생성하지 못함
|
||||
|
||||
@@ -8,21 +8,25 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.eactive.eai.common.util.JacksonUtil;
|
||||
import com.eactive.eai.message.EncodingVar;
|
||||
import com.eactive.eai.message.StandardItem;
|
||||
import com.eactive.eai.message.StandardMessage;
|
||||
import com.eactive.eai.message.StandardType;
|
||||
import com.fasterxml.jackson.core.JsonFactory;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
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.JsonNodeFactory;
|
||||
import com.fasterxml.jackson.databind.node.JsonNodeType;
|
||||
|
||||
public class JsonReader implements StandardReader {
|
||||
static Logger logger = LoggerFactory.getLogger(JsonReader.class);
|
||||
|
||||
// ObjectMapper 는 설정이 끝나면 thread-safe 하고 생성 비용이 크므로 재사용한다.
|
||||
// 숫자 보존 옵션(입력/출력)은 JacksonUtil.newNumberSafeMapper() 참조.
|
||||
private static final ObjectMapper MAPPER = JacksonUtil.newNumberSafeMapper();
|
||||
|
||||
private char FIELD_SEPARATOR = '.';
|
||||
private boolean ZERO_BASE_INDEX = true;
|
||||
|
||||
@@ -39,15 +43,9 @@ public class JsonReader implements StandardReader {
|
||||
}
|
||||
|
||||
JsonNode jsonNode = null;
|
||||
ObjectMapper mapper = null;
|
||||
ObjectMapper mapper = MAPPER;
|
||||
JsonFactory factory = null;
|
||||
JsonParser parser = null;
|
||||
mapper = new ObjectMapper();
|
||||
mapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);
|
||||
// USE_BIG_DECIMAL_FOR_FLOATS 만 켜면 기본 JsonNodeFactory 가 DecimalNode 생성 시
|
||||
// stripTrailingZeros() 를 적용해 scale 이 음수가 되고, 그 결과
|
||||
// 100000000.00 이 1E+8 로 변형된다. exact 모드로 수신한 자릿수를 보존한다.
|
||||
mapper.setNodeFactory(JsonNodeFactory.withExactBigDecimals(true));
|
||||
factory = mapper.getFactory();
|
||||
try {
|
||||
parser = factory.createParser(jsonString);
|
||||
|
||||
Reference in New Issue
Block a user