简体   繁体   English

获取反序列化器中的属性类型(杰克逊)

[英]Get type of property in deserializer (Jackson)

I'm looking for a way to deserialize a subclass using a deserializer registered using the @JsonDeserialize annotation on the abstract super class.我正在寻找一种使用抽象超类上的@JsonDeserialize注释注册的反序列化器来反序列化子类的方法。 If there are better options I'm happy to adapt these options – but I'm not aware of any solution to this problem at the moment.如果有更好的选择,我很乐意调整这些选择——但目前我不知道有任何解决方案可以解决这个问题。

The core problem is: There is an abstract super class A :核心问题是:有一个抽象超类A

@JsonSerialize(using = SerializerForA.class)
@JsonDeserialize(using = DeserializerForA.class)
public abstract class A {
  private String value;

  protected A(String value) {
    this.value = value;
  }
  ...
}

(The annotations are my attempt to do custom deserialization – maybe it's the wrong approach). (注释是我尝试进行自定义反序列化 - 也许这是错误的方法)。

There are some derived classes, and A doesn't know any of the derived classes.有一些派生类,而A不知道任何派生类。 Think about A is part of a framework and the derived classes are client code using the framework.想想A是框架的一部分,派生类是使用框架的客户端代码。 Here are two derived classes:这里有两个派生类:

public class B extends A {
  public B(String value) {
    super(value);
  }
  ...
}

and

public class C extends A {
  public C(String value) {
    super(value);
  }
  ...
}

These derived classes are used in a "container" class, eg:这些派生类用于“容器”类,例如:

public class MyClass {
  private B b;
  private C c;

  ...
}

And the corresponding JSON looks like this:对应的 JSON 如下所示:

{
  "b": "value_of_b",
  "c": "value_of_c"
}

Writing a serializer is relatively simple:写一个序列化器比较简单:

public class SerializerForA extends JsonSerializer<A> {
  @Override
  public void serialize(A obj, JsonGenerator gen, SerializerProvider serializers) throws IOException {
    gen.writeString(obj.getValue());
  }
}

The deserializer would look like this:反序列化器看起来像这样:

public class DeserializerForA extends JsonDeserializer<A> {
  @Override
  public A deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
    A result = ???
    return result;
  }
}

But how does the deserializer know, which type the resulting object has?但是反序列化器如何知道结果对象的类型呢? Is it possible to get the class name from one of the parameters (eg DeserializationContext )?是否可以从参数之一(例如DeserializationContext )中获取类名?

There are some ways the code can be changed, if it helps.如果有帮助,有一些方法可以更改代码。 For example, a setter can be used for the value field, instead of the constructor, but I would prefer a constructor, or some factory method ( public static A getInstance(String value) { ... } ).例如,setter 可用于value字段,而不是构造函数,但我更喜欢构造函数或某些工厂方法( public static A getInstance(String value) { ... } )。


Edit (1) The deserializer should be called without any specific code automatically by the ObjectMapper , like:编辑(1) ObjectMapper应该在没有任何特定代码的情况下自动调用反序列化器,例如:

ObjectMapper mapper = new ObjectMapper();
MyClass myClass = mapper.readValue(json, MyClass.class);

That means, Jackson knows the type of the container class.这意味着,杰克逊知道容器类的类型。 It also knows the type of the properties a and b .它还知道属性ab的类型。 The reason to use a custom deserializer is that I need to have control over the instance creation process (basically, I want to reuse the same instance of each object for the same value – similar to an enum).使用自定义反序列化器的原因是我需要控制实例创建过程(基本上,我想为相同的值重用每个对象的相同实例——类似于枚举)。


Edit (2) Changing the JSON structure is not an option.编辑 (2)更改 JSON 结构不是一种选择。 But I don't think it should be necessary.但我认为没有必要。 If I didn't need to have control over instance creation, the whole implementation would just work out of the box.如果我不需要控制实例创建,那么整个实现就可以开箱即用。 Additional type information in the JSON should not be necessary. JSON 中的其他类型信息应该不是必需的。


Edit (3) The purpose of all of this is to implement a framework that can be used by application to create typesafe objects that are stored as JSON.编辑 (3)所有这些的目的是实现一个框架,应用程序可以使用该框架来创建存储为 JSON 的类型安全对象。 Normally, I would use a Java enum for this purpose, but it is possible, that clients need to read JSON documents that are created by a new version of the framework (with new values), but the client didn't update the framework version yet.通常,我会为此目的使用 Java enum ,但有可能客户端需要读取由新版本的框架(使用新值)创建的 JSON 文档,但客户端没有更新框架版本然而。

Example:例子:

There is a class called Currency :有一个名为Currency的类:

public class Currency extends A {
  public static final Currency EUR = new Currency("EUR");
}

It is used like this:它是这样使用的:

public class Transaction {
  private Currency currency;
  private double amount;
}

The JSON would look like this: JSON 看起来像这样:

{
  "currency": "EUR",
  "amount": 24.34
}

Now a new currency is added:现在添加了一种新货币:

public class Currency extends A {
  public static final Currency EUR = new Currency("EUR");
  public static final Currency USD = new Currency("USD");
}

Clients with the new framework can produce the following JSON:使用新框架的客户端可以生成以下 JSON:

{
  "currency": "USD",
  "amount": 48.93,
}

One client didn't update to the new framework version.一位客户没有更新到新的框架版本。 This client should be able to read the JSON without crashing.这个客户端应该能够读取 JSON 而不会崩溃。

To sum up, the ObjectMapper is provided with an instance of MyClass containing one B and one C .总而言之, ObjectMapper提供了一个MyClass的实例,其中包含一个B和一个C

Jackson will call the JsonDeserializer<A> both for B and C providing the string "value_of_b" / "value_of_c" (because by reflection, it will know that B and C are instances of A and that's the only deserializer available in the context). Jackson 将为BC调用JsonDeserializer<A> ,提供字符串"value_of_b" / "value_of_c" (因为通过反射,它将知道BCA的实例,这是上下文中唯一可用的反序列化器)。

Considering that in the Jackson deserializer you are in a static context (you don't have any concrete instance of A in there, you're just deserializing some string text with information that allows you to create a new instance of MyClass that looks like the serialized instance that they provided you with), then I think the only option you have is to create a factory method somewhere in your code as you guessed (I'd create it directly in the A class):考虑到在 Jackson 反序列化器中,您处于静态上下文中(其中没有A的任何具体实例,您只是在反序列化一些带有信息的字符串文本,这些信息允许您创建一个新的MyClass实例,看起来像他们为您提供的序列化实例),那么我认为您唯一的选择是在您的代码中的某处创建一个工厂方法(我会直接在A类中创建它):

public static A getInstance(String value) {
    ...
}

and then inside the deserializer, simply instantiate it from that independently on whether the serialized instance was a B or a C (cause at the end of the day, you only know A so you can't handle anything else):然后在反序列化器中,只需根据序列化实例是B还是C独立地实例化它(因为归根结底,您只知道A ,因此您无法处理其他任何事情):

public final class ADeserializer extends JsonDeserializer<A> {
    @Override
    public A deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
        String value = jsonParser.getText();
        return A.getInstance(value);
    }
}

So basically each implementation will provide you with the String value that you need to create an A , and of course you will have to create a concrete basic implementation of A on your side in order to instantiate it (because you don't know what the other implementations are, and because you need it to be concrete to create an instance).所以基本上每个实现都会为您提供创建A所需的String value ,当然您必须在您身边创建A的具体基本实现才能实例化它(因为您不知道其他实现是,并且因为您需要它具体来创建实例)。

You have to include some information during the serialization in the json.您必须在 json 中的序列化过程中包含一些信息。 There are two ways to achieve that.有两种方法可以实现这一目标。

First is to enable default typing.首先是启用默认类型。 This will add class names to your json.这会将类名添加到您的 json 中。 It will look like this:它看起来像这样:

{
  "a": [
    "A",
    {
      "value": "a"
    }
  ],
  "b": [
    "B",
    {
      "value": "b"
    }
  ]
}

You can enable it on ObjectMapper by calling activateDefaultTyping(ptv, DefaultTyping.OBJECT_AND_NON_CONCRETE)您可以通过调用activateDefaultTyping(ptv, DefaultTyping.OBJECT_AND_NON_CONCRETE)ObjectMapper上启用它

Second one is to add per-class annotations.第二个是添加每类注释。 You can achieve that by adding those annotations to your abstract class.您可以通过将这些注释添加到您的抽象类来实现。

@JsonTypeInfo(
  use = JsonTypeInfo.Id.NAME, 
  include = JsonTypeInfo.As.PROPERTY, 
  property = "type")
@JsonSubTypes({ 
  @Type(value = A.class, name = "a"), 
  @Type(value = B.class, name = "b") 
})

Then the serializer will produce json like this:然后序列化器会产生这样的json:

{
  "a": {
    "type": "a",
    "value": "value_of_a"
  }
  "b": {
    "type": "b",
    "value": "value_of_b"
  }
}

A simple solution – that even doesn't need a lot of magic – is to use a factory method and @JsonCreator :一个简单的解决方案——甚至不需要很多魔法——是使用工厂方法和@JsonCreator

The base class is already known, and also the serializer:基类是已知的,序列化器也是:

  @JsonSerialize(using = SerializerForA.class)
  public class A {
    protected String value;

    public String getValue() {
      return value;
    }
  }

  public class SerializerForA extends JsonSerializer<A> {
    @Override
    public void serialize(A a, JsonGenerator gen, SerializerProvider serializers)
        throws IOException {
      gen.writeString(a.getValue());
    }
  }

The inherited classes need to implement a factory method each:继承的类每个都需要实现一个工厂方法:

  public class B extends A {
    @JsonCreator
    public static B create(String value) {
      B b = new B();
      b.value = value;
      return b;
    }
  }

and

  public class C extends A {
    @JsonCreator
    public static C create(String value) {
      C c = new C();
      c.value = value;
      return c;
    }
  }

Now the following JSON is parsed successfully:现在成功解析了以下JSON:

{
  "b":"This is B",
  "c":"This is C"
}

The obvious downside is, that inherited classes have to implement the factory method.明显的缺点是,继承的类必须实现工厂方法。 I'd like to avoid that.我想避免这种情况。

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

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