카테고리 없음

[Error] handleException org.springframework.core.codec.DecodingException: JSON decoding error: Cannot deserialize value of type

늘이 2024. 11. 22. 17:18

 

 

[2024-11-22 15:47:36:218069] ERROR 69254 --- [nio-8080-exec-4] [0:0:0:0:0:0:0:1] c.d.s.c.e.GlobalExceptionHandler : handleException org.springframework.core.codec.DecodingException: JSON decoding error: Cannot deserialize value of type java.util.ArrayList<com.test.project.registrationstatement.dto.response.ExtractIndexResponseDto$ContentText> from String value (token JsonToken.VALUE_STRING) at org.springframework.http.codec.json.AbstractJackson2Decoder.processException(AbstractJackson2Decoder.java:275) Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: Error has been observed at the following site(s): *__checkpoint ⇢ Body from POST http://localhost:8000/extract/index [DefaultClientResponse] Original Stack Trace: at .. 생략

 

응답 데이터에서 reference_files > content > text가 일부는 리스트 형태이고 일부는 문자열로 오는 경우, 이를 처리하려면 Jackson 또는 데이터 파싱 로직에서 두 가지 타입을 모두 허용하도록 유연하게 구현해야 합니다. 아래와 같은 방안을 적용할 수 있습니다.

 

1. Jackson의 @JsonDeserialize를 활용하여 유연한 타입 처리

  • ExtractIndexResponseDto.ContentText의 text 필드에 커스텀 디시리얼라이저를 적용해, JSON이 문자열 또는 리스트일 때 모두 처리하도록 작성합니다.
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class ContentText {
    private int page;

    @JsonDeserialize(using = TextDeserializer.class)
    private List<String> text;
}

// Custom Deserializer
public static class TextDeserializer extends JsonDeserializer<List<String>> {
    @Override
    public List<String> deserialize(JsonParser parser, DeserializationContext context) throws IOException {
        ObjectCodec codec = parser.getCodec();
        JsonNode node = codec.readTree(parser);

        if (node.isArray()) {
            // JSON이 배열인 경우
            List<String> result = new ArrayList<>();
            for (JsonNode element : node) {
                result.add(element.asText());
            }
            return result;
        } else if (node.isTextual()) {
            // JSON이 문자열인 경우
            return Collections.singletonList(node.asText());
        } else {
            throw new JsonMappingException(parser, "Unexpected JSON type for 'text'. Expected array or string.");
        }
    }
}

 

 

2. Jackson의 Object 타입 활용 후 데이터 타입에 따라 처리

text 필드를 Object로 선언하고, 런타임 시 타입을 체크하여 변환합니다.

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class ContentText {
    private int page;
    private Object text;

    public List<String> getTextAsList() {
        if (text instanceof String) {
            return Collections.singletonList((String) text);
        } else if (text instanceof List) {
            return (List<String>) text;
        } else {
            throw new IllegalArgumentException("Unsupported type for 'text'");
        }
    }
}

getTextAsList 메서드는 사용 시점에서 타입 변환을 수행합니다. 데이터를 사용하는 곳에서 getTextAsList()를 호출해 리스트로 변환된 데이터를 안전하게 사용할 수 있습니다.

 

3. WebClient 레벨에서 데이터 변환

Spring WebFlux의 WebClient에서 JSON 데이터를 수동으로 파싱하여 유연하게 처리할 수도 있습니다.

ExtractIndexResponseDto response = webClient.post()
        .uri(extractIndexUrl)
        .body(BodyInserters.fromMultipartData(bodyBuilder.build()))
        .retrieve()
        .bodyToMono(String.class) // JSON을 문자열로 먼저 받기
        .map(json -> {
            try {
                ObjectMapper mapper = new ObjectMapper();
                // String을 ArrayList<ExtractIndexResponseDto>로 변환
                ExtractIndexResponseDto result = mapper.readValue(json, ExtractIndexResponseDto.class);
                return result;
            } catch (JsonProcessingException e) {
                throw new RuntimeException("JSON Parsing Error", e);
            }
        })
        .block();

4. API 변경 요청

가능하다면, API 개발자에게 명확한 데이터 타입을 사용하도록 요청하는 것이 가장 이상적인 해결책입니다. 응답 데이터에서 text 필드가 항상 리스트로 오도록 통일하면 처리 로직이 간단해집니다.