Kafka消费者泛型JSON反序列化异常:LinkedHashMap转自定义类失败
泛型JSON反序列化问题解决方案
问题场景
我有一个Kafka消费者库,用于消费字节数组。上游始终会将JSON字符串转换为字节进行发布。消费后转换为字符串时,我需要将其反序列化为不同消费者对应的不同类。
尝试的反序列化实现:
public class MessageUtil<T> { public T deserializeByStringConversion(final byte[] message) throws Exception { String json = IOUtils.toString(message); return new ObjectMapper().readValue(json, new TypeReference<T>() {}); } }
调用方式:
MessageUtil<MyClass> messageUtil; @BeforeClass public void setup() { MockitoAnnotations.initMocks(this); } @Test public void testMyClass() throws Exception { MyClass myClass = messageUtil.deserializeByStringConversion(IOUtils.toByteArray(request)); Assert.assertNotNull(myClass); }
运行时抛出异常:
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.a.b.c.d.MyClass
问题原因
Java泛型存在类型擦除机制:编译后MessageUtil<T>中的泛型参数T会被擦除为Object。此时new TypeReference<T>() {}无法获取到实际的MyClass类型信息,Jackson只能将JSON反序列化为默认的LinkedHashMap,强转时就会抛出类型转换异常。
解决方案
方案1:传入目标类的Class对象(推荐,简单直接)
修改工具类,让调用者传入要反序列化的目标类的Class实例,Jackson可以直接基于该Class完成类型匹配:
public class MessageUtil { // 复用ObjectMapper实例,避免重复创建开销 private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); public <T> T deserializeByStringConversion(final byte[] message, Class<T> targetClass) throws Exception { String json = IOUtils.toString(message); return OBJECT_MAPPER.readValue(json, targetClass); } }
调用方式:
MessageUtil messageUtil = new MessageUtil(); @Test public void testMyClass() throws Exception { MyClass myClass = messageUtil.deserializeByStringConversion(IOUtils.toByteArray(request), MyClass.class); Assert.assertNotNull(myClass); }
方案2:通过子类保留泛型类型信息
如果希望工具类实例绑定固定类型,可以通过创建子类的方式保留泛型参数的实际类型:
首先修改MessageUtil,在构造时解析父类的泛型参数:
public class MessageUtil<T> { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); private final TypeReference<T> typeReference; @SuppressWarnings("unchecked") public MessageUtil() { Type superClass = getClass().getGenericSuperclass(); if (!(superClass instanceof ParameterizedType)) { throw new IllegalArgumentException("MessageUtil必须继承为带具体类型的子类"); } ParameterizedType parameterizedType = (ParameterizedType) superClass; this.typeReference = new TypeReference<T>() {} { @Override public Type getType() { return parameterizedType.getActualTypeArguments()[0]; } }; } public T deserializeByStringConversion(final byte[] message) throws Exception { String json = IOUtils.toString(message); return OBJECT_MAPPER.readValue(json, typeReference); } }
创建对应类型的子类:
public class MyClassMessageUtil extends MessageUtil<MyClass> {}
调用方式:
MessageUtil<MyClass> messageUtil = new MyClassMessageUtil(); @Test public void testMyClass() throws Exception { MyClass myClass = messageUtil.deserializeByStringConversion(IOUtils.toByteArray(request)); Assert.assertNotNull(myClass); }
方案3:传入TypeReference支持复杂泛型
如果需要反序列化List<MyClass>这类复杂泛型结构,可以让调用者直接传入TypeReference实例:
public class MessageUtil { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); public <T> T deserializeByStringConversion(final byte[] message, TypeReference<T> typeReference) throws Exception { String json = IOUtils.toString(message); return OBJECT_MAPPER.readValue(json, typeReference); } }
调用方式:
MessageUtil messageUtil = new MessageUtil(); @Test public void testMyClass() throws Exception { MyClass myClass = messageUtil.deserializeByStringConversion(IOUtils.toByteArray(request), new TypeReference<MyClass>() {}); Assert.assertNotNull(myClass); } // 复杂泛型示例 @Test public void testMyClassList() throws Exception { List<MyClass> myClassList = messageUtil.deserializeByStringConversion(IOUtils.toByteArray(listRequest), new TypeReference<List<MyClass>>() {}); Assert.assertNotNull(myClassList); }
内容的提问来源于stack exchange,提问作者Arnav Sengupta
相关产品推荐
相关产品推荐

