简体   繁体   English

如何在Jackson中为参数化接口类型进行常规的自定义反序列化

[英]How to do a general custom deserializer for a parameterized interface type in Jackson

I have an interface with numerous implementation classes and I would like to write a general deserializer instead of a deserializer for each implementation: 我有一个包含许多实现类的接口,我想为每个实现编写一个通用的反序列化器,而不是一个反序列化器:

The interface: 界面:

public interface IEnumerable<E extends Enum<E>> {
    public String getName();
}

Abstract class that uses reflection to do a reverse lookup on the Enumerable: 使用反射对Enumerable进行反向查找的抽象类:

public abstract class AbstractEnumerable<E extends Enum<E> & IEnumerable<E>> {
    private static final XLogger log = XLoggerFactory.getXLogger(AbstractEnumerable.class.getCanonicalName());

    private final TypeToken<AbstractEnumerable<E>> typeToken = new TypeToken<AbstractEnumerable<E>>(getClass()) { };

    public final E getByName(String name) {
        TypeToken<?> genericParam = typeToken.resolveType(AbstractEnumerable.class.getTypeParameters()[0]);
        log.debug("Runtime class of generic IEnumerable parameter: {}", genericParam.getType().getTypeName());

        try {
            log.trace("Getting a Class object for {}", genericParam.getType().getTypeName());
            Class<E> clazz = (Class<E>)Class.forName(genericParam.getType().getTypeName());

            log.trace("Iterating over the enum constants in {}", genericParam.getType().getTypeName());
            for(Object o : Arrays.asList(clazz.getEnumConstants())) {
                E val = clazz.cast(o);
                if(val.getName().equals(name) || val.name().equals(name)) {
                    return val;
                }
            }
        } catch(ClassNotFoundException e) {
            log.error("Unable to find the class definition for {}", genericParam.getType().getTypeName());
            log.catching(e);
        }

        return null;
    }
}

Implementation: 执行:

public class DnsRecordTypeEnumeration extends AbstractEnumerable<DnsRecordTypeEnumeration.DnsRecordType> {
    public static enum DnsRecordType implements IEnumerable<DnsRecordType> {
        DNS_TYPE_A("A"),
        DNS_TYPE_AAAA("AAAA"),
        DNS_TYPE_CNAME("CNAME");

        private final String localizedName;

        private DnsRecordType(final String localizedName) {
            this.localizedName = localizedName;
        }

        @Override
        public final String getName() {
            return localizedName;
        }
    }
}

Is it possible to have a general custom deserializer for all Enumerable implementations? 是否可以为所有Enumerable实现使用通用的自定义反序列化器? I need to access the enclosing class to do the reverse lookup. 我需要访问封闭的类来进行反向查找。

I tried this: 我尝试了这个:

public abstract class AbstractJacksonJsonDeserializer<T> extends JsonDeserializer<T> {
        private static final XLogger log = XLoggerFactory.getXLogger(AbstractJacksonJsonDeserializer.class
                .getCanonicalName());

        private final TypeToken<AbstractJacksonJsonDeserializer<T>> typeToken =
                new TypeToken<AbstractJacksonJsonDeserializer<T>>(getClass()) { };

        protected Class<T> getTypeClass() {
            Class<T> clazz = (Class<T>)RuntimeClassFactory.getInstance().create(typeToken, AbstractJacksonJsonDeserializer.class, 0);
            log.debug("Runtime class of object to be deserialized: {}", clazz.getCanonicalName());
            return (Class<T>)RuntimeClassFactory.getInstance().create(typeToken, AbstractJacksonJsonDeserializer.class, 0);
        }

public class EnumerableJacksonJsonDeserializer<E extends Enum<E> & IEnumerable<E>> extends AbstractJacksonJsonDeserializer<E> {

    @Override
    public E deserialize(final JsonParser parser, final DeserializationContext
            context) throws JsonProcessingException, IOException {
        JsonNode node = parser.getCodec().readTree(parser);
        String name = node.textValue();
        return getEnumeration().getByName(name);
    }

    protected <T extends AbstractEnumerable<E>> T getEnumeration() {
        Class<E> enumerableClass = getTypeClass();
        Class<T> enumerationClass = (Class<T>) enumerableClass.getEnclosingClass();
        return EnumerationFactory.getInstance().create(enumerationClass);
    }
}

Problem is that I can't annotate fields with the above class like this because my custom deserializer takes a type parameter. 问题是我无法使用上述类对字段进行注释,因为我的自定义反序列化器带有类型参数。 This results in an compile error: 这会导致编译错误:

@JsonDeserialize(using = EnumerableJacksonJsonDeserializer.class)
private DnsRecordType recordType;

You can write a module that provides deserializers based on the context they are encountered in: this allows you to get the concrete type when a subclass of IEnumerable is encountered. 您可以编写一个模块,该模块基于遇到的反序列化器提供的上下文:在遇到IEnumerable的子类时,这可以使您获得具体的类型。

I'm assuming some of the external bits you refer to do actually exist, like EnumerationFactory. 我假设您引用的某些外部位确实存在,例如EnumerationFactory。 Tbh the whole thing with "enumeration" wrapping an enum confused me, but this seems to follow what you put down: 整个过程都用“枚举”包装了一个枚举,这让我感到困惑,但这似乎跟着您所说的:

@Test
public void calls_general_deserializer_for_parameterized_interface() throws Exception {
    ObjectMapper mapper = new ObjectMapper();
    mapper.registerModule(new SimpleModule() {
        @Override
        public void setupModule(SetupContext context) {
            context.addDeserializers(new Deserializers.Base() {
                @Override
                public JsonDeserializer<?> findEnumDeserializer(Class<?> type,
                        DeserializationConfig config,
                        BeanDescription beanDesc) throws JsonMappingException {
                    if (IEnumerable.class.isAssignableFrom(type)) return new EnumerableDeserializer(type);
                    return null;
                }
            });
            super.setupModule(context);
        }
    });
    TestData data = mapper.readerFor(TestData.class).with(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES)
            .with(JsonParser.Feature.ALLOW_SINGLE_QUOTES).readValue("{ recordType: 'AAAA' }");
    assertThat(data.recordType, equalTo(DnsRecordType.DNS_TYPE_AAAA));
}

public static final class EnumerableDeserializer<E extends Enum<E> & IEnumerable<E>> extends
        StdScalarDeserializer<E> {
    private final Class<? extends AbstractEnumerable<E>> enumerationClass;
    private final Class<E> enumerableClass;

    public EnumerableDeserializer(Class<E> enumerableClass) {
        super(enumerableClass);
        this.enumerableClass = enumerableClass;
        this.enumerationClass = (Class<? extends AbstractEnumerable<E>>) enumerableClass.getEnclosingClass();
    }

    @Override
    public E deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        AbstractEnumerable<E> enumerable = getEnumeration();
        return enumerable.getByName(p.getText());
    }

    private AbstractEnumerable<E> getEnumeration() {
        return EnumerationFactory.getInstance().create(enumerationClass);
    }
}

Jackson's existing Enum deserialization doesn't seem to support allowing each enum instance to have two possible string values, which seems to be what you need here. Jackson的现有Enum反序列化似乎不支持允许每个枚举实例具有两个可能的字符串值,这似乎是您在这里需要的。 TBH it seems you could dump the enclosing enumeration class and just write the deserializer more simply: TBH似乎您可以转储封闭的枚举类,而只是更简单地编写反序列化器:

public static final class DirectEnumerableDeserializer<E extends Enum<E> & IEnumerable<E>> extends
        StdScalarDeserializer<E> {
    private final Class<E> enumerableClass;
    private final ImmutableList<E> values;
    private final ImmutableMap<String, E> names;

    public DirectEnumerableDeserializer(Class<E> enumerableClass) {
        super(enumerableClass);
        this.enumerableClass = enumerableClass;
        this.values = ImmutableList.copyOf(enumerableClass.getEnumConstants());
        ImmutableMap.Builder<String, E> names = ImmutableMap.builder();
        for (E value : values) {
            names.put(value.name(), value);
        }
        this.names = names.build();
    }

    @Override
    public E deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        String key = p.getText();
        E value = names.get(key);
        if (value == null) {
            value = findByLocalisedName(key);
            if (value == null) throw ctxt.weirdStringException(key, enumerableClass, "Unrecognised name");
        }
        return value;
    }

    private E findByLocalisedName(String key) {
        for (E value : values) {
            if (value.getName().equals(key)) return value;
        }
        return null;
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM