Logo

dev-resources.site

for different kinds of informations.

Polymorphic deserialization with Jackson and no annotations

Published at
10/16/2020
Categories
java
jackson
polymorphism
deserialization
Author
reda
Author
4 person written this
reda
open
Polymorphic deserialization with Jackson and no annotations

Suppose I asked you to take a cup from the multiple choices in the header photo, certainly you'll ask which one to choose as there is many cups and you need something precise and maybe unique by cup to decide.

All the choices are cups, they share some common points (color, weight, ...) but maybe each one has something that the other cups don't have. This is polymorphism.

Full source code available here.

What's polymorphism ?

Polymorphism is the ability to have different implementations represented by a single interface or abstract class.

This post is about how to deserialize objects by their abstract. Same idea presented below can be used to serialize objects.

Let's consider the following abstraction :

Player abstraction

Suppose you have an POST endpoint which supports creation of multiple objects by abstraction. Something like : you POST /players when PLAYER can be FootballPlayer, TennisPlayer or BasketPlayer.

@PostMapping(value = "/players", consumes = MediaType.APPLICATION_JSON_VALUE)
  ResponseEntity<?> createPlayer(@RequestBody Player player);
Enter fullscreen mode Exit fullscreen mode

Problem

If we go directly invoking the /players endpoint, we'll face the InvalidDefinitionException as Jackson can't define to which class instance the PLAYER request body should be deserialized.

What's Jackson ?

As claimed by it's creators :

Jackson has been known as "the Java JSON library" or "the best JSON parser for Java". Or simply as "JSON for Java".

Simply, Jackson is a Java library to serialize and deserialize objects to/from JSON.

I'll use Spring Boot in this post but if you want to go without it, just grab the latest dependency of Jackson Databind on Maven Central.

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-core</artifactId>
  <version>VERSION_HERE</version>
</dependency>
Enter fullscreen mode Exit fullscreen mode

Deserialization

The deserialization part will have to determine which concrete class the JSON represents, and instantiate an instance of it.

Deserialization using annotation

Using annotation is probably the most used technique because of it's simplicity and time-saving, but sometimes it's impossible to go with it.

Some resources describe how it can be done, you can check here or here if you are curious.

Deserialization with no annotation

Not every project gives the possibility to add annotations on your domain classes.

Sometimes, your domain classes (Player, FootballPlayer, ...) are hidden behind an imported jar dependency and you can't access them for annotating

OR

you are using DDD and hexagonal architecture where purists say :

No framework or libraries inside the domain

OR

simply because of some technical constraints in your company/project.

First thing to do is to create a custom Jackson deserializer and implement the logic of deserialization.

To implement a custom deserializer, we need to create an implementation of StdDeserializer which is an abstract type and the base class for common Jackson deserializers.

public class PlayerDeserializer extends StdDeserializer<Player> {

  public PlayerDeserializer() {
    this(null);
  }

  public PlayerDeserializer(final Class<?> vc) {
    super(vc);
  }

  @Override
  public Player deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
      throws IOException {
    // TODO - implement deserialization logic here
    return null;
  }
Enter fullscreen mode Exit fullscreen mode

Few lines above, we exposed our abstraction of Player. Let's create an enum to distinguish the different Player instances.

public enum SportType {

  FOOTBALL("FOOTBALL"),
  TENNIS("TENNIS"),
  BASKET("BASKET");

  private final String value;

  SportType(String value) {
    this.value = value;
  }

  @Override
  public String toString() {
    return String.valueOf(value);
  }

  public static SportType fromValue(String text) {
    for (SportType sportType : SportType.values()) {
      if (String.valueOf(sportType.value).equals(text)) {
        return sportType;
      }
    }
    return null;
  }
Enter fullscreen mode Exit fullscreen mode

As now we defined our SportType enum, based on that we can implement in details our deserialize method. Here we can take the benefit of this sportType field inside our abstract class PLAYER.

@Override
  public Player deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
      throws IOException {

    final ObjectMapper mapper = (ObjectMapper) jsonParser.getCodec();
    final JsonNode playerNode = mapper.readTree(jsonParser);

    final SportType sportType = SportType.fromValue(playerNode.get("sportType").asText());
    switch (sportType) {
      case FOOTBALL:
        return mapper.treeToValue(playerNode, FootballPlayer.class);
      case TENNIS:
        return mapper.treeToValue(playerNode, TennisPlayer.class);
      default:
        log.warn("Unexpected Player type : {}", sportType.toString());
        throw new IllegalStateException("Unexpected Player type : " + sportType.toString());
    }
  }
Enter fullscreen mode Exit fullscreen mode

The last step to do before going ready for deserialization is to indicate that our PlayerDeserializer is a part of Jackson serializers/deserializers. Two possibilities available here :

  • Register the PlayerDeserializer manually while adding it to the ObjectMapper (Example in the source code).

OR

  • As we use Spring Boot, we can use the @JsonComponent which do the same behavior but with less lines of code.

Maybe you're asking why we have this annotation as the title indicates "FREE ANNOTATIONS"? .

@JsonComponent is a Spring Boot annotation and not a part of Jackson and it lives in our newly created deserializer.

Another question can be :

What if we don't have this kind of "type" in our abstract class (e.g sportType) ?

In this case, we can change a little bit our deserialize method to inspect fields of each PLAYER instance (looking for something unique by each instance).

  @Override
  public Player deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
      throws IOException {

    final ObjectMapper mapper = (ObjectMapper) jsonParser.getCodec();
    final JsonNode playerNode = mapper.readTree(jsonParser);

    final SportType sportType = SportType.fromValue(playerNode.get("sportType").asText());

    if (playerNode.has("position")) {
      return mapper.treeToValue(playerNode, FootballPlayer.class);
    }
    if (playerNode.has("atpPoints")) {
      return mapper.treeToValue(playerNode, TennisPlayer.class);
    }
    log.warn("Unexpected Player type : {}", sportType.toString());
    throw new IllegalStateException("Unexpected Player type : " + sportType.toString());
  }
Enter fullscreen mode Exit fullscreen mode

Conclusion

In this post, we learned how to handle, in the same endpoint, variable requests (polymorphism) in Jackson by using custom deserializer and without polluting our domain classes with annotations and external libraries.

Resources

Source code : https://github.com/redamessoudi/polymorphic-deserialization

Header photo by Eric Prouzet on Unsplash

jackson Article's
30 articles in total
Favicon
Why Do We Still Need Jackson or Gson in Java?
Favicon
A simple GeoJSON serializer for Jackson
Favicon
[Java Spring Boot] Como Criar Serializador Personalizado para seus Responses ou Json de saída
Favicon
[Java Spring Boot] How to implement a Custom Serializer for your Responses or Json
Favicon
[Java SpringBoot] Como Criar Deserializador Personalizado para seus Requests
Favicon
[Java SpringBoot] How to implement a Custom Deserializer for your Requests
Favicon
Java Jackson JSON: How to Handle Custom Keys?
Favicon
Create a custom Jackson JsonSerializer und JsonDeserializer for mapping values
Favicon
Anotación @JsonUnwrapped
Favicon
A tale of fixing a tiny OpenAPI bug
Favicon
Kotlin Springboot -- Part 21 任意の key value の json を POST する API E2E を書く
Favicon
Formatting json Date/LocalDateTime/LocalDate in Spring Boot
Favicon
Jackson's @JsonView with SpringBoot Tutorial
Favicon
Jackson JSON parsing top-level map into records
Favicon
Using Jackson Subtypes to Write Better Code
Favicon
Java – Convert Excel File to/from JSON (String/File) – using Apache Poi + Jackson
Favicon
How to resolve Json Infinite Recursion problem when working with Jackson
Favicon
Java – Convert Excel File to/from JSON (String/File) – using Apache Poi + Jackson
Favicon
Practical Java 16 - Using Jackson to serialize Records
Favicon
Kotlin – Convert Object to/from JSON with Jackson 2.x
Favicon
💾 Java Records 💿 with Jackson 2.12
Favicon
Jackson, JSON and the Proper Handling of Unknown Fields in APIs
Favicon
Polymorphic deserialization with Jackson and no annotations
Favicon
Playing around with Kotlin Sealed Classes
Favicon
Moonwlker: JSON without annotation
Favicon
Jackson Readonly properties and swagger UI
Favicon
Registering Jackson sub-types at runtime in Kotlin
Favicon
Parsing JSON in Spring Boot, part 1
Favicon
Customize how Jackson does LocalDate Parsing
Favicon
Painless JSON with Kotlin and jackson

Featured ones: