You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kafka JSON反序列化为Java对象失败:无字符串参数构造方法

问题描述

从Kafka消费JSON字符串反序列化为Java对象时失败,报错信息:

Cannot construct instance of sample.ms.dto.FooDTO (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('{
"type": "CASA",
"batchList": [
"CVA348856650375401ABC",
"BNM346846350035548DEF"
]
}')

待反序列化的原始JSON:

{"type":"CASA","batchList":["CVA348856650375401ABC","BNM346846350035548DEF"]}

反序列化代码:

ObjectMapper objectMapper = new ObjectMapper();
FooDTO fooDto = objectMapper.readValue(payload.value(), new TypeReference<FooDTO>() {});

消费核心代码片段:

@Incoming(KafkaConstants.KAFKA_TOPIC_REQUEST)
@Acknowledgment(Acknowledgment.Strategy.POST_PROCESSING)
@Blocking
@Retry
public Response consumeRequest(Record<String, String> payload) {
    log.info("+++++ START CONSUMING REQUEST +++++");
    log.debug("{} : {}", payload.key(), payload.value());
    try {
        if (Optional.ofNullable(payload.value()).isPresent()) {
            ObjectMapper objectMapper = new ObjectMapper();
            FooDTO fooDto = objectMapper.readValue(payload.value(), new TypeReference<FooDTO>() {});
            // ...后续业务逻辑
        }
    } catch (Exception e) {
        // 异常处理
    }
}

FooDTO定义:

@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Valid
public class FooDTO {

    //@NotNull
    private String batchKey;

    //@NotNull
    private String type;

    //@NotNull
    private List<String> batchList;
}

已尝试操作:

  • 手动写入相同JSON字符串反序列化成功
  • 使用replace、trim、strip、正则替换等方式去除换行,无效,字符串仍保留换行且报错

原因分析

错误核心是Jackson将payload.value()识别为单个String值,而非JSON对象。这意味着消费到的内容大概率是被双重序列化后的结果:生产者先把JSON对象序列化为带引号、转义的字符串,再用StringSerializer发送到Kafka,导致消费端拿到的是包裹JSON的字符串,而非原始JSON对象。

例如实际消费到的内容可能是:

"{\"type\":\"CASA\",\"batchList\":[\"CVA348856650375401ABC\",\"BNM346846350035548DEF\"]}"

此时Jackson会尝试用String构造FooDTO,但FooDTO无String参数构造函数,因此报错。


解决方案

方案1:修复生产者序列化逻辑(推荐)

让生产者直接发送JSON对象,而非序列化后的字符串。确保生产者使用JsonSerializer(如Spring Kafka的JsonSerializer)序列化FooDTO,避免先用ObjectMapper转成字符串再发送。

方案2:消费端做双重反序列化

若无法修改生产者代码,可先解析外层字符串得到真实JSON,再反序列化为FooDTO:

ObjectMapper objectMapper = new ObjectMapper();
// 先解析外层字符串,拿到内部JSON内容
String realJson = objectMapper.readValue(payload.value(), String.class);
// 再反序列化为FooDTO
FooDTO fooDto = objectMapper.readValue(realJson, FooDTO.class);

方案3:检查消费者配置

确保消费者的value.deserializer与生产者匹配:

  • 若生产者用JsonSerializer,消费者对应使用JsonDeserializer(Quarkus环境可配置JsonbDeserializer)
  • Quarkus配置示例(application.properties):
# 使用Jsonb反序列化器
mp.messaging.incoming.kafka-topic-request.value.deserializer=io.quarkus.kafka.client.serialization.JsonbDeserializer
# 指定目标DTO类
mp.messaging.incoming.kafka-topic-request.value.deserializer.class=sample.ms.dto.FooDTO

额外检查点

  • 打印payload.value()的原始内容,确认是否存在多余引号、转义字符
  • 验证Lombok注解生效:检查编译后的FooDTO class文件,确认@NoArgsConstructor、@AllArgsConstructor已生成对应构造函数

内容的提问来源于stack exchange,提问作者Eve

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 12:31:02