java - 如何编写 Jackson 转换器来处理泛型集合类型?

标签 java generics jackson jackson-databind

我创建了一个 Jackson Converter 哪个订单Set元素按升序排序,以便生成的 JSON 数组按排序顺序输出,而不是给定 Set 碰巧使用的任意迭代顺序执行。它扩展了标准 StdConverter jackson 的类(class),由 Converter documentation 推荐.

NOTE: implementors are strongly encouraged to extend StdConverter instead of directly implementing Converter, since that can help with default implementation of typically boiler-plate code.


public class OrderedSetConverter
        extends StdConverter<Set<DayOfWeek>, Set<DayOfWeek>> {
    @Override
    public Set<DayOfWeek> convert(Set<DayOfWeek> value) {
        return value == null ? null : value.stream()
                .sorted(Comparator.nullsLast(Comparator.naturalOrder()))
                .collect(Collectors.toCollection(LinkedHashSet::new));
    }
}

public class MyType {
    @JsonSerialize(converter = OrderedSetConverter.class)
    private Set<DayOfWeek> myValues;
}
这适用于 Converter用于转换特定类型的元素(在本例中为 DayOfWeek)。但是,此实现中没有任何特定于特定类型的内容;它适用于任何 Comparable 类型。因此,我更喜欢这个转换器的通用实现,可以用于任何 Set的可比性。
我试图通过使用泛型的简单用法来实现这一点:
public class OrderedSetConverter<E extends Comparable<? super E>>
        extends StdConverter<Set<E>, Set<E>> {
    @Override
    public Set<E> convert(Set<E> value) {
        return value == null ? null : value.stream()
                .sorted(Comparator.nullsLast(Comparator.naturalOrder()))
                .collect(Collectors.toCollection(LinkedHashSet::new));
    }
}
这不像我希望的那样工作,因为 jackson 尝试(但失败)将集合的元素类型转换为 Comparable , 而不是实际类型(例如 DayOfWeek )。
Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `java.lang.Comparable` (no Creators, like default constructor, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
 at [Source: (String)"{"daysOfWeek":["MONDAY","TUESDAY","WEDNESDAY","THURSDAY","FRIDAY","SATURDAY","SUNDAY"]}"; line: 1, column: 16] (through reference chain: com.example.MyType["daysOfWeek"]->java.util.HashSet[0])
    at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
    at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1764)
    at com.fasterxml.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:400)
    at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1209)
    at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserialize(AbstractDeserializer.java:274)
    at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer._deserializeFromArray(CollectionDeserializer.java:347)
    at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:244)
    at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:28)
    at com.fasterxml.jackson.databind.deser.std.StdDelegatingDeserializer.deserialize(StdDelegatingDeserializer.java:175)
    at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:129)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:324)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:187)
    at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:322)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4593)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3548)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3516)
我能想到的最好的方法是将它用作抽象父类(super class),并有一个 Converter实际使用的每种类型的每个特定子类型:
public abstract class OrderedSetConverter<E extends Comparable<? super E>>
        extends StdConverter<Set<E>, Set<E>> {
    @Override
    public Set<E> convert(Set<E> value) {
        return value == null ? null : value.stream()
                .sorted(Comparator.nullsLast(Comparator.naturalOrder()))
                .collect(Collectors.toCollection(LinkedHashSet::new));
    }
}

public class DayOfWeekSetConverter extends OrderedSetConverter<DayOfWeek> { }

public class MyType {
    @JsonSerialize(converter = DayOfWeekSetConverter.class)
    private Set<DayOfWeek> myValues;
}
如何为通用集合类型编写转换器并让 Jackson 找出元素类型?

最佳答案

OrderedSetConverter不知道在反序列化过程中必须实例化哪种类型。我们需要指示反序列化过程使用哪种类型。如果要保持转换器通用,则需要在创建转换器时提供此信息。要扩展默认行为,您需要实现自定义 com.fasterxml.jackson.databind.cfg.HandlerInstantiator :

class ConverterHandlerInstantiator extends HandlerInstantiator {

    @Override
    public Converter<?, ?> converterInstance(MapperConfig<?> config, Annotated annotated, Class<?> implClass) {
        if (config instanceof DeserializationConfig) {
            JsonDeserialize jsonDeserialize = annotated.getAnnotation(JsonDeserialize.class);
            Class contentAs = jsonDeserialize.contentAs();
            if (contentAs != Void.class) {
                return new OrderedSetConverter<>(contentAs);
            }
        }
        return super.converterInstance(config, annotated, implClass);
    }

    @Override
    public JsonDeserializer<?> deserializerInstance(DeserializationConfig config, Annotated annotated, Class<?> deserClass) {
        return null;
    }

    @Override
    public KeyDeserializer keyDeserializerInstance(DeserializationConfig config, Annotated annotated, Class<?> keyDeserClass) {
        return null;
    }

    @Override
    public JsonSerializer<?> serializerInstance(SerializationConfig config, Annotated annotated, Class<?> serClass) {
        return null;
    }

    @Override
    public TypeResolverBuilder<?> typeResolverBuilderInstance(MapperConfig<?> config, Annotated annotated, Class<?> builderClass) {
        return null;
    }

    @Override
    public TypeIdResolver typeIdResolverInstance(MapperConfig<?> config, Annotated annotated, Class<?> resolverClass) {
        return null;
    }
}
正如您注意到的,上面的代码使用 contentAs属性(property)来自 JsonDeserialize注解。我们需要在你的类中指定它:
class MyType {
    @JsonSerialize(converter = OrderedSetConverter.class)
    @JsonDeserialize(converter = OrderedSetConverter.class, contentAs = DayOfWeek.class)
    private Set<DayOfWeek> myValues;
}
当我们序列化对象时不需要这个值,所以我们可以跳过它。现在,我们需要注册我们的自定义实例化器:
ObjectMapper mapper = new ObjectMapper();
mapper.setHandlerInstantiator(new ConverterHandlerInstantiator());
最后,我们的通用转换器:
class OrderedSetConverter<E extends Comparable<? super E>> extends StdConverter<Set<E>, Set<E>> {
    private final Class<E> contentClass;

    OrderedSetConverter() {
        this(null);
    }

    public OrderedSetConverter(Class<E> contentClass) {
        this.contentClass = contentClass;
    }

    @Override
    public Set<E> convert(Set<E> value) {
        return value == null ? null : value.stream()
                .sorted(Comparator.nullsLast(Comparator.naturalOrder()))
                .collect(Collectors.toCollection(LinkedHashSet::new));
    }

    @Override
    public JavaType getInputType(TypeFactory typeFactory) {
        if (contentClass == null) {
            return super.getInputType(typeFactory);
        }
        return typeFactory.constructCollectionType(Set.class, contentClass);
    }
}

关于java - 如何编写 Jackson 转换器来处理泛型集合类型?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67758020/

相关文章:

java - Android 从数据库中删除一行

c# - 有没有办法调用通用的methodInfo?

java - Guice 注入(inject)通用类型

java - Spring 不使用带注解的构造函数(@JsonCreator)进行实例化

java - jackson 在python中的替代品是什么?

android - 如何修复使用 Jackson/Gson 库解析的不准确的日期时间(分钟)?

Java VisualVM CPU 使用率和处理器亲和性

java - 使用 spymemcached 和 HashAlgorithm.KETAMA_HASH 时如何处理恢复 memcached 节点

java - 如何在多个字段上过滤搜索查询?

.net - 如何找到泛型参数的 TypeSpec