【问题标题】:How to use Jackson's ContextualDeserializer for root values?如何将 Jackson 的 ContextualDeserializer 用于根值?
【发布时间】:2015-09-26 19:10:42
【问题描述】:

我正在尝试为从某个抽象类扩展或实现某个接口的所有类实现通用反序列化器。在这个例子中,我使用接口StringConvertible。我需要确定具体类型,以便创建实例。

old forum post by Programmer Bruce 引导我使用 ContextDeserializer 并且当 StringConvertible 是另一个类中的属性时它正在工作。

但是当我想直接反序列化StringConvertible时,我找不到获取具体类型的方法,因为beanProperty参数是null。根据 Jackson JSON 用户组的this question/answer,显然这是意料之中的: The only case where property should be null is when serializing a "root value", meaning the object instance passed directly to ObjectMapper's (or ObjectWriter's) writeValue() method -- in this case there simply isn't a referring property. But otherwise it should always be passed.

两种情况的示例请参见下面的 main 方法:

@JsonDeserialize(using = StringConvertibleDeserializer.class)
public final class SomeStringConvertible implements StringConvertible {

    private final String value;

    public SomeStringConvertible(final String value) {

        this.value = value;
    }

    @Override
    @JsonValue
    public String stringValue() {

        return value;
    }
}

public final class SomeWrapper {

    public SomeStringConvertible stringConvertible;

    public SomeWrapper() {

    }
}


public class StringConvertibleDeserializer extends StdDeserializer<StringConvertible> implements ContextualDeserializer {

    private final Class<? extends StringConvertible>    targetClass;

    StringConvertibleDeserializer() {

        super(StringConvertible.class);

        this.targetClass = null;
    }

    StringConvertibleDeserializer(final Class<? extends StringConvertible> targetClass) {

        super(StringConvertible.class);

        this.targetClass = targetClass;
    }

    @Override
    public JsonDeserializer<?> createContextual(final DeserializationContext deserializationContext, @Nullable final BeanProperty beanProperty)
                                                                                                                                                throws JsonMappingException {

        final StringConvertibleDeserializer contextualDeserializer;

        // ====  Determine target type  =====
        final Class<? extends StringConvertible> targetClass;
        JavaType type = beanProperty.getType(); // -> beanProperty is null when the StringConvertible type is a root value
        targetClass = (Class<? extends StringConvertible>) type.getRawClass();

        // ====  Create contextual deserializer  =====
        contextualDeserializer = new StringConvertibleDeserializer(targetClass);

        // ====  Return  =====
        return contextualDeserializer;
    }

    @Override
    public StringConvertible deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException, JsonProcessingException {

        final StringConvertible value;

        // ====  Create instance using the target type  =====
        if (targetClass.equals(SomeStringConvertible.class))
            value = new SomeStringConvertible(jsonParser.getText());
        else {
            throw new RuntimeException();
        }

        // ====  Return  =====
        return value;
    }

}

public final class JacksonModule extends SimpleModule {

    public JacksonModule() {

        super();

        addDeserializer(StringConvertible.class, new StringConvertibleDeserializer());
    }
}

public final class Main {

    public static void main(String[] args) {

        final ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new JacksonModule());

        final String wrappedValueJSON = "{\"stringConvertible\":\"hello world\"}";
        final String rootValueJSON = "\"hello world\"";

        try {
            mapper.readValue(wrappedValueJSON, SomeWrapper.class);  // This works fine
            mapper.readValue(rootValueJSON, SomeStringConvertible.class);   // This causes a NPE in createContextual(...) because beanProperty is null

        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

问题:如果是根值,如何获取类型具体类型?或者,如果有比这更好的解决方案,您会建议什么?

【问题讨论】:

    标签: java json serialization jackson


    【解决方案1】:

    简单的回答:你不知道。

    更长的答案可以解释为什么会这样;但要点是(反)序列化程序需要以他们知道注册目的的方式进行注册;它们不是通用的,并且被告知它们正在处理什么类型。他们需要在注册时就知道。

    这可以通过更改您注册反序列化程序的方式来实现。您可以实现Deserializers,而不是使用SimpleModule,并使用给定的类型信息处理反序列化器的构造。 这样会有多个反序列化器实例,配置有用于注册的类型信息。这也是预期的类型。

    【讨论】:

    • 我为我的问题找到了一个简单的解决方案。不过谢谢:您的解决方案将在要求更高的情况下派上用场。对于任何对示例感兴趣的人,它是在 jackson-datatype-guava:GuavaDeserializers 中完成的,它在 GuavaModule:context.addDeserializers(new GuavaDeserializers()); 中注册
    • 没有问题,很高兴你知道了。令人印象深刻的侦探工作也令人印象深刻——我自己已经忘记了那个额外的方法。它确实派上用场,需要牢记这一点。
    【解决方案2】:

    在研究 StaxMan 提出的解决方案时,我偶然发现了 this Github issue for jackson-databind,它解决了完全相同的问题。作为回应,维护者在2.5.0版本中为DeserializationContext添加了一个方法:

    This turned out relatively easy to implement, so now there is:
    
    class DeserializationContext {
       public JavaType getContextualType() { ... }
    }
    which will give expected type during call to createContextual(), including case of deserializers that are directly added via annotation.
    

    因此,要在我的情况下完成这项工作,我只需要更改 createContextual(...) 方法中的一些代码。我改变了这个:

       // ====  Determine target type  =====
        final Class<? extends StringConvertible> targetClass;
        JavaType type = beanProperty.getType(); // -> beanProperty is null when the StringConvertible type is a root value
        targetClass = (Class<? extends StringConvertible>) type.getRawClass();
    

    到这里:

    // ====  Determine target type  =====
    final Class<? extends StringConvertible> targetClass;
    {
        // ====  Get the contextual type info  =====
        final JavaType type; 
        if (beanProperty != null) 
            type = beanProperty.getType();  // -> beanProperty is null when the StringConvertible type is a root value
    
        else {
            type = deserializationContext.getContextualType();
        }
    
        // ====  Get raw Class from type info  =====
        targetClass = (Class<? extends StringConvertible>) type.getRawClass();
    }
    

    【讨论】:

    • 这里的侦探工作令人印象深刻——感谢您分享这个!
    • 感谢您分享这个!这是我试图解决这个特殊问题时的最后一块拼图:gist.github.com/darylteo/a7be65b539c0d8d3ca0de94d96763f33
    • 始终使用 deserializationContext.getContextualType() 并忽略 beanProperty 似乎也有效,并且使代码更简单。
    【解决方案3】:

    找到了一种更简单的方法来创建通用反序列化器,实现 ContextualDeserializer 并将 de JavaType 保留在变量实例中:

    public static class MappedByDeserializer extends StdDeserializer<Object> implements ContextualDeserializer {
        private JavaType typeToUse;
    
        protected MappedByDeserializer() {
            super( Object.class );
        }
        
        @Override
        public Object deserialize(JsonParser aParser, DeserializationContext aContext) throws IOException, JsonProcessingException {
            Object lObj = aContext.readValue( aParser, typeToUse );
            // adjust your object
            return lObj;
        }
    
        @Override public JsonDeserializer<?> createContextual( DeserializationContext ctxt, BeanProperty property ) throws JsonMappingException {
            typeToUse = property.getType();
            return this;
        }   
    }
    

    【讨论】:

      猜你喜欢
      • 2011-10-06
      • 2018-11-17
      • 1970-01-01
      • 1970-01-01
      • 2021-11-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-01-11
      相关资源
      最近更新 更多