如何使用Jackson的ContextualDeserializer作为根值?

时间:2015-07-08 17:51:04

标签: java json serialization jackson

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

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

但是当我想直接反序列化StringConvertible时,我无法找到获取具体类型的方法,因为beanProperty参数是null。根据杰克逊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.

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

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

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

2 个答案:

答案 0 :(得分:13)

在研究StaxMan提出的解决方案时,我偶然发现了jackson-databind DeserializationContext,它解决了完全相同的问题。作为回应,维护者在版本2.5.0中向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();
}

对此:

 def calculate
      first_player_code  = Code.find(params[:first_player_id])
      second_player_code = Code.find(params[:second_player_id])
      File.open("first_player_code.rb", "w"){|file| file << first_player_code}
      File.open("second_player_code.rb", "w"){|file| file << second_player_code}
      system("ruby calculate.rb")
 end

答案 1 :(得分:0)

简单回答:你没有。

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

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