Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jackson Custom Deserializer for polymorphic objects and String literals as defaults

I'd like to deserialize an object from YAML with the following properties, using Jackson in a Spring Boot application:

  • Abstract class Vehicle, implemented by Boat and Car
  • For simplicity, imagine both have a name, but only Boat has also a seaworthy property, while Car has a top-speed.
mode-of-transport:
  type: boat
  name: 'SS Boatface'
  seaworthy: true
----
mode-of-transport:
  type: car`
  name: 'KITT'
  top-speed: 123

This all works fine in my annotated subclasses using @JsonTypeInfo and @JsonSubTypes!

Now, I'd like to create a shorthand using only a String value, which should create a Car by default with that name:

mode-of-transport: 'KITT'

I tried creating my own custom serializer, but got stuck on most of the relevant details. Please help me fill this in, if this is the right approach:

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? */
   }
}

I found these 2 answers for inspiration, but it looks like combining it with polymorphic types makes it more difficult: How do I call the default deserializer from a custom deserializer in Jackson and Deserialize to String or Object using Jackson

A few things are different than the solutions offered in those questions:

  • I am processing YAML, not JSON. Not sure about the subtle differences there.
  • I have no problem hardcoding the 'default' type for Strings inside my Deserializer, hopefully making it simpler.
like image 445
ChrisDekker Avatar asked Oct 15 '25 08:10

ChrisDekker


1 Answers

This was actually easier than I thought to solve it. I got it working using the following:

  1. Custom deserializer implementation:
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. To avoid circular dependencies and to make the custom deserializer work with the polymorphic @JsonTypeInfo and @JsonSubTypes annotations I kept those annotations on the class level of Vehicle, but put the following annotations on the container object I am deserializing:
public class Transport {

    @JsonDeserialize(using = VehicleDeserializer.class)
    @JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
    private Vehicle modeOfTransport;

    // Getter, setters
}

This means that by default a Vehicle is deserialized as a polymorphic object, unless explicitly specified to deserialize it using my custom deserializer. This deserializer will then in turn defer to the polymorphism if the input is not a String.

Hopefully this will help someone running into this issue :)

like image 169
ChrisDekker Avatar answered Oct 18 '25 10:10

ChrisDekker



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!