【问题标题】:Jackson Custom Deserializer for polymorphic objects and String literals as defaultsJackson Custom Deserializer 用于多态对象和默认字符串字面量
【发布时间】:2020-06-09 15:04:37
【问题描述】:

我想在 Spring Boot 应用程序中使用 Jackson 来反序列化具有以下属性的 YAML 对象:

  • 抽象类Vehicle,由BoatCar实现
  • 为简单起见,假设两者都有一个名称,但只有Boat 也有一个seaworthy 属性,而Car 有一个top-speed
mode-of-transport:
  type: boat
  name: 'SS Boatface'
  seaworthy: true
----
mode-of-transport:
  type: car`
  name: 'KITT'
  top-speed: 123

这一切在我使用 @JsonTypeInfo@JsonSubTypes 的带注释的子类中都可以正常工作!

现在,我想创建一个仅使用 String 值的速记,默认情况下应该使用该名称创建一个 Car

mode-of-transport: 'KITT'

我尝试创建自己的自定义序列化程序,但在大多数相关细节上陷入困境。如果这是正确的方法,请帮我填写:

public class VehicleDeserializer extends StdDeserializer<Merger> {

   /* Constructors here */

   @Override
   public Vehicle deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
      if (/* it is an OBJECT */){
         // Use the default polymorphic deserializer 
      } else if (/* it is a STRING */) {
         Car car = new Car();
         car.setName( /* the String value */ );
         return car;
      }
      return ???; /* what to return here? */
   }
}

我找到了这 2 个答案以获得灵感,但看起来将它与多态类型结合起来会变得更加困难:How do I call the default deserializer from a custom deserializer in JacksonDeserialize to String or Object using Jackson

与这些问题中提供的解决方案有些不同:

  • 我正在处理 YAML,而不是 JSON。不确定那里的细微差别。
  • 我在反序列化器中对字符串的“默认”类型进行硬编码没有问题,希望它更简单。

【问题讨论】:

    标签: java spring jackson yaml


    【解决方案1】:

    这实际上比我想象的要容易解决。我使用以下方法让它工作:

    1. 自定义反序列化器实现:
    public class VehicleDeserializer extends StdDeserializer<Vehicle> {
    
        public VehicleDeserializer() {
            super(Vehicle.class);
        }
    
        @Override
        public Vehicle deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
            if (jp.currentToken() == JsonToken.VALUE_STRING) {
                Car car = new Car();
                car.setName(jp.readValueAs(String.class));
                return car;
            }
            return jp.readValueAs(Vehicle.class);
        }
    }
    
    1. 为了避免循环依赖并使自定义反序列化器与多态@JsonTypeInfo@JsonSubTypes 注释一起工作,我将这些注释保留在Vehicle 的类级别上,但将以下注释放在我正在反序列化的容器对象上:
    public class Transport {
    
        @JsonDeserialize(using = VehicleDeserializer.class)
        @JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
        private Vehicle modeOfTransport;
    
        // Getter, setters
    }
    

    这意味着默认情况下,车辆被反序列化为多态对象,除非明确指定使用我的自定义反序列化器对其进行反序列化。如果输入不是字符串,则此反序列化器将依次遵循多态性。

    希望这会帮助遇到这个问题的人:)

    【讨论】:

      【解决方案2】:

      所以有一个解决方案要求您使用DeserializationProblemHandler 处理杰克逊错误(因为您想使用不同的输入解析相同的类型,所以使用常规方法不容易实现):

      public class MyTest {
      
          @Test
          public void doTest() throws JsonParseException, JsonMappingException, IOException {
              final ObjectMapper om = new ObjectMapper();
              om.addHandler(new DeserializationProblemHandler() {
      
                  @Override
                  public Object handleMissingInstantiator(final DeserializationContext ctxt, final Class<?> instClass, final JsonParser p, final String msg) throws IOException {
                      if (instClass.equals(Car.class)) {
                          final JsonParser parser = ctxt.getParser();
                          final String text = parser.getText();
                          switch (text) {
                          case "KITT":
                              return new Car();
                          }
                      }
                      return NOT_HANDLED;
                  }
      
                  @Override
                  public JavaType handleMissingTypeId(final DeserializationContext ctxt, final JavaType baseType, final TypeIdResolver idResolver, final String failureMsg) throws IOException {
                      // if (baseType.isTypeOrSubTypeOf(Vehicle.class)) {
                      final JsonParser parser = ctxt.getParser();
                      final String text = parser.getText();
                      switch (text) {
                      case "KITT":
                          return TypeFactory.defaultInstance().constructType(Car.class);
                      }
                      return super.handleMissingTypeId(ctxt, baseType, idResolver, failureMsg);
                  }
              });
      
              final Container objectValue = om.readValue(getObjectJson(), Container.class);
      
              assertTrue(objectValue.getModeOfTransport() instanceof Car);
      
              final Container stringValue = om.readValue(getStringJson(), Container.class);
      
              assertTrue(stringValue.getModeOfTransport() instanceof Car);
          }
      
          private String getObjectJson() {
              return "{ \"modeOfTransport\": { \"type\": \"car\", \"name\": \"KITT\", \"speed\": 1}}";
          }
      
          private String getStringJson() {
              return "{ \"modeOfTransport\": \"KITT\"}";
          }
      }
      
      class Container {
      
          private Vehicle modeOfTransport;
      
          public Vehicle getModeOfTransport() {
              return modeOfTransport;
          }
      
          public void setModeOfTransport(final Vehicle modeOfTransport) {
              this.modeOfTransport = modeOfTransport;
          }
      }
      
      @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type", visible = true)
      @JsonSubTypes({
              @Type(name = "car", value = Car.class)
      })
      abstract class Vehicle {
      
          protected String type;
          protected String name;
      
          public String getType() {
              return type;
          }
      
          public void setType(final String type) {
              this.type = type;
          }
      
          public String getName() {
              return name;
          }
      
          public void setName(final String name) {
              this.name = name;
          }
      }
      
      @JsonTypeName("car")
      class Car extends Vehicle {
      
          private int speed;
      
          public int getSpeed() {
              return speed;
          }
      
          public void setSpeed(final int speed) {
              this.speed = speed;
          }
      }
      

      请注意,我使用的是 JSON,而不是 YAML,您还需要添加其他子类型。

      【讨论】:

      • 感谢您的意见。请参阅我的答案,了解我设法开始工作的更简单的方法。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-04-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多