java - 如何将 Jackson 的 ContextualDeserializer 用于根值?

标签 java json serialization jackson

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

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

但是当我想直接反序列化StringConvertible时,我找不到获取具体类型的方法,因为beanProperty参数是null。根据this question/answer,显然这是预期的在 Jackson JSON 用户组: 属性应该为 null 的唯一情况是序列化“根值”时,这意味着对象实例直接传递给 ObjectMapper(或 ObjectWriter)的 writeValue() 方法——在这种情况下根本没有引用属性。但除此之外,它应该始终通过。

有关这两种情况的示例,请参见下面的主要方法:

@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);
        }
    }
}

问题:如果是根值,我怎样才能得到类型具体类型?或者,如果有比这更好的解决方案,您会推荐什么?

最佳答案

在研究 StaxMan 提出的解决方案时,我偶然发现了 this Github issue 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();
}

关于java - 如何将 Jackson 的 ContextualDeserializer 用于根值?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31300046/

相关文章:

java - 如果数据库包含现有数据,如何仅在 Activity 中调用一次方法?

java - 将 json 对象发送到 Java 服务器独立应用程序时出错

java - 当只有子类实现可序列化时,序列化如何工作

java - 如何阻止 hibernate 工具实现 Serialized 接口(interface)

java - 新行字符\n 在 Android 的 TextView 中的阿拉伯文本中不起作用

java - 制作一个空字符串常量值得吗?

java - 在 java Rest API 中使用 json POST 请求的问题

java - 保留 Joda-Time `DateTime` 对象的最有效方法是什么?

java - Nashorn:对于使用 lambda 公开的函数,JavaScript 表达式求值抛出类转换异常

json - JQ:如果某些键值相同则合并条目