使用 Jackson 序列化通用接口子类

Posted

技术标签:

【中文标题】使用 Jackson 序列化通用接口子类【英文标题】:Serialising generic interface sub-class with Jackson 【发布时间】:2013-01-23 13:59:48 【问题描述】:

我有一个带有多个实现类的通用接口,我需要通过 Json 对其进行序列化和反序列化。我正在尝试开始使用 Jackson,使用完整的数据绑定,但运气不佳。

示例代码说明了问题:

import org.codehaus.jackson.map.*;
import org.codehaus.jackson.map.type.TypeFactory;
import org.codehaus.jackson.type.JavaType;

public class Test 

    interface Result<T> 

    static class Success<T> implements Result<T> 
        T value;
        T getValue() return value;
        Success(T value) this.value = value;
    

    public static void main(String[] args) 
        Result<String> result = new Success<String>("test");
        JavaType type = TypeFactory.defaultInstance().constructParametricType(Result.class, String.class);
        ObjectMapper mapper = new ObjectMapper().enableDefaultTyping();
        ObjectWriter writer = mapper.writerWithType(type);
        ObjectReader reader = mapper.reader(type);

        try 
            String json = writer.writeValueAsString(result);
            Result<String> result2 = reader.readValue(json);
            Success<String> success = (Success<String>)result2;
         catch (Throwable ex) 
            System.out.print(ex);
        
    

writeValueAsString 的调用导致以下异常:

org.codehaus.jackson.map.JsonMappingException: 没有为类 Test$Success 找到序列化程序,也没有发现用于创建 BeanSerializer 的属性(为避免异常,请禁用 SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS)

为什么 Jackson 期望我注册一个序列化程序 - 我虽然完全数据绑定的重点是我不需要这样做?

上述方法正确吗?

【问题讨论】:

【参考方案1】:

首先,您需要使用工厂方法TypeFactory.constructSpecializedType 注册专用类型以与Jackson 一起使用。然后,专门的类型应该是一个 bean(它应该有一个默认的构造函数、getter 和 setter)来反序列化它。

看看这些测试澄清剂。

@Test
public void canSerializeParametricInterface() throws IOException 
    final ObjectMapper mapper = new ObjectMapper().enableDefaultTyping();
    final JavaType baseInterface = TypeFactory.defaultInstance().constructParametricType(Result.class, String.class);
    final JavaType subType = TypeFactory.defaultInstance().constructSpecializedType(baseInterface, Success.class);
    final ObjectWriter writer = mapper.writerWithType(subType);
    final String json = writer.writeValueAsString(Success.create("test"));
    Assert.assertEquals("\"value\":\"test\"", json);


@Test
public void canDeserializeParametricInterface() throws IOException 
    final ObjectMapper mapper = new ObjectMapper().enableDefaultTyping();
    final JavaType baseInterface = TypeFactory.defaultInstance().constructParametricType(Result.class, String.class);
    final JavaType subType = TypeFactory.defaultInstance().constructSpecializedType(baseInterface, Success.class);
    final ObjectReader reader = mapper.reader(subType);
    final Success<String> success = reader.readValue("\"value\":\"test\"");
    Assert.assertEquals("test", success.getValue());


public static interface Result<T> 


public static class Success<T> implements Result<T> 

    private T value;

    public static <T> Success<T> create(T value) 
        final Success<T> success = new Success<T>();
        success.value = value;
        return success;
    

    public T getValue() 
        return value;
    

    public void setValue(T value) 
        this.value = value;
    

【讨论】:

感谢您的示例。对我来说,问题是我的类都是不可变的——没有默认构造函数,也没有设置器——只有设置所有字段的构造函数。有没有办法让你的方法使用不可变类? 您的示例的另一个问题是,在反序列化 json 时,您知道目标是 Success 类。在我的例子中,有几个类实现了 Result 接口,所以我需要 Jackson 来确定使用哪一个,可能通过在 json 中合并子类型。 经过各种尝试,我最终得到了使用 mixins 的多态东西。最后,我放弃了这一切,转而启用 ObjectMapper.DefaultTyping.NON_FINAL,它将所有非原始对象的对象类型合并到 JSON 中。尽管如此,还是会接受这个答案,因为它很有帮助。

以上是关于使用 Jackson 序列化通用接口子类的主要内容,如果未能解决你的问题,请参考以下文章

Jackson 没有反序列化已序列化的通用列表

使用 Jackson 将通用 java 对象序列化为 JSON

jackson对Exception类型对象的序列化与反序列化

使用 Jackson 反序列化对象列表

尝试反序列化子类时“com.fasterxml.jackson.databind.exc.InvalidTypeIdException:无法解析类型 ID”

Spring Boot Jackson 通用数据类型序列化