AdBlock Detected

It looks like you're using an ad-blocker!

Our team work realy hard to produce quality content on this website and we noticed you have ad-blocking enabled. Advertisements and advertising enable us to continue working and provide high-quality content.

This Refactorizando entry on Serialization and Deserialization in Inheritance with Jackson is a continuation of the previous article on Serialization and Deserialization with Jackson.

As we mentioned in the previous article, Jackson is a library that helps us serialize and deserialize objects to map them to our class. In this article, we will explore what happens when we have inheritance.

We will cover two types of inheritance: when we want to exclude information and when we want to include it.

In this article, we will use Lombok in some of our examples.

Let’s start with the Maven dependencies for Jackson:

   <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-core</artifactId>
      <version>2.12.1</version>
    </dependency>

    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.12.1</version>
    </dependency>

Inclusion of Information in Serialization and Deserialization

Using ObjectMapper globally

For our examples, we will use a class called “Animal,” which will have two subclasses, “Dog” and “Ostrich.” We will also create a class “Vet” that will contain a list of animals.

Let’s start by defining our parent class:

@Getter
@Setter
public abstract class Animal {
    private String type;
    private int legsNumber;

    protected Animal(String type, int legsNumber) {
        this.type = type;
        this.legsNumber = legsNumber;
    }
}

Next, we’ll create the “Dog” class:

@Getter
@Setter
public class Dog extends Animal {
    private String model;

    public Dog(String type, int legsNumber, String model) {
        super(type, legsNumber);
        this.model = model;
    }

}

Now, let’s create the “Ostrich” subclass:

@Getter
@Setter
public class Ostrich extends Animal {
    private int age;

    public Ostrich(String type, int legsNumber, int age) {
        super(type, legsNumber);
        this.age = age;
    }

}

Finally, we’ll create the “Vet” class, which will contain a list of animals:

@Getter
@Setter
public class Vet {

    private List<Animal> animals;
    
   
}

To declare the information only once and globally, we will perform an activation in an ObjectMapper, which is the best option when dealing with multiple involved types. This activation will be done using the activateDefaultTyping method.

The activateDefaultTyping method accepts different parameters, and it is mandatory to use PolymorphicTypeValidator as a security parameter for all cases.

Let’s proceed to globally activate the mapper and also instantiate two objects, one Dog, and one Ostrich, and add them to the Vet class. All of this will be done through a test class:

  @Test
  public void shouldSerializeAnimals() {

    PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator
        .builder()
        .allowIfSubType(Animal.class)
        .build();

    ObjectMapper mapper =
        JsonMapper.builder()
            .activateDefaultTyping(ptv, DefaultTyping.NON_CONCRETE_AND_ARRAYS)
            .build();

    Dog dog = new Dog("mammal", 4, "Huski");
    Ostrich ostrich = new Ostrich("oviparous ", 2, 5);

    List<Animal> animals = new ArrayList<>();
    animals.add(dog);
    animals.add(ostrich);

    Vet vet = new Vet();
    vet.setAnimals(animals);
    mapper.writerWithDefaultPrettyPrinter().writeValueAsString(vet);

    System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(vet));

  }

Output:
{
  "animals" : [ "java.util.ArrayList", [ [ "com.refactorizando.jackson.Dog", {
    "type" : "mammal",
    "legsNumber" : 4,
    "model" : "Huski"
  } ], [ "com.refactorizando.jackson.Ostrich", {
    "type" : "oviparous ",
    "legsNumber" : 2,
    "age" : 5
  } ] ] ]
}

Annotations on Classes

Although the above approach works and is easy to understand, it adds a lot of boilerplate code, which can sometimes make it more complicated than necessary.

Jackson provides annotations that simplify programming for serializing and deserializing classes. For inheritance, we will use the @JsonTypeInfo annotation for the parent class and the @JsonSubTypes annotation for the child classes. This information should be added to the parent class, as shown in the previous example:

@Getter
@Setter
@JsonTypeInfo(
  use = JsonTypeInfo.Id.NAME, 
  include = JsonTypeInfo.As.PROPERTY, 
  property = "type")
@JsonSubTypes({ 
  @Type(value = Dog.class, name = "dog"), 
  @Type(value = Ostrich.class, name = "ostrich") 
})
public abstract class Animal {
    private String type;
    private int legsNumber;

    protected Animal(String type, int legsNumber) {
        this.type = type;
        this.legsNumber = legsNumber;
    }
}

A notable point is that we have added the “type” property, mapped with the values “dog” and “ostrich.” This way, when our JSON is displayed, it will have a new field.

Let’s execute the following test:

  @Test
  public void shouldSerializeAnimals()  throws JsonProcessingException {

    ObjectMapper mapper = new ObjectMapper();

    DogInclusionAnnotation dog = new DogInclusionAnnotation("mammal", 4, "Huski");
    OstrichInclusionAnnotation ostrich = new OstrichInclusionAnnotation("oviparous ", 2, 5);

    List<AnimalInclusionAnnotation> animals = new ArrayList<>();
    animals.add(dog);
    animals.add(ostrich);

    VetInclusionAnnotation vet = new VetInclusionAnnotation();
    vet.setAnimalInclusionAnnotations(animals);

    System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(vet));

  }
}

Output

Output:
{
  "animalInclusionAnnotations" : [ {
    "animalType" : "dog",
    "type" : "mammal",
    "legsNumber" : 4,
    "model" : "Huski"
  }, {
    "animalType" : "ostrich",
    "type" : "oviparous ",
    "legsNumber" : 2,
    "age" : 5
  } ]
}

Ignoring Properties in Inheritance

In many cases, we need to ignore certain fields in our classes so that they are not mapped by the incoming or outgoing values. For such cases, let’s explore different ways to ignore them.

Ignoring Properties with Annotations

In general, using annotations is the most convenient and practical way, and it helps reduce the amount of code.

Let’s use the previous example’s parent class “Animal” and its child classes:

@JsonIgnoreProperties({ "type", "model" })
@Getter
@Setter
public class DogIgnoreAnnotation extends AnimalIgnoreAnnotation{

  private String model;

  private String name;

  public DogIgnoreAnnotation(String type, int legsNumber, String model, String name) {
    super(type, legsNumber);
    this.model = model;
    this.name = name;
  }
}

With the above annotations, we ignore both the parent and child properties using JsonIgnoreProperties. Additionally, if we want to ignore a specific property, we can use @JsonIgnore.

Let’s see how to execute it:

  @Test
  public void shouldSerializeAnimals() throws JsonProcessingException {

    ObjectMapper mapper = new ObjectMapper();

    DogIgnoreAnnotation dog = new DogIgnoreAnnotation("mammal", 4, "Huski", "bruno");
    OstrichIgnoreAnnotation ostrich = new OstrichIgnoreAnnotation("oviparous ", 2, 5);

    List<AnimalIgnoreAnnotation> animals = new ArrayList<>();
    animals.add(dog);
    animals.add(ostrich);

    VetIgnoreAnnotation vet = new VetIgnoreAnnotation();
    vet.setAnimalIgnoreAnnotationList(animals);
    mapper.writerWithDefaultPrettyPrinter().writeValueAsString(vet);

    System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(vet));

  }
Output:
{
  "animalIgnoreAnnotationList" : [ {
    "legsNumber" : 4,
    "name" : "bruno"
  }, {
    "type" : "oviparous ",
    "legsNumber" : 2,
    "age" : 5
  } ]
}

Ignoring Properties Using Jackson Introspection

Ignoring properties in your class or classes through Jackson introspection is not very common or well-known, but it is a powerful way to ignore properties. This method allows us to use hasIgnoreMarker to mark the properties to be avoided.

To use Jackson introspection, we need to extend the JacksonAnnotationIntrospector class.

For the following example, let’s ignore the “type” and “model” of a dog as we did previously:

class JacksonIntrospectorExample extends JacksonAnnotationIntrospector {
    public boolean hasIgnoreMarker(AnnotatedMember annotatedMember) {
        return mannotatedMember.getDeclaringClass() == Animal.class && annotatedMember.getName() == "type" 
          || annotatedMember.getDeclaringClass() == Dog.class 
          || annotatedMember.getName() == "legsNumber" 
          || super.hasIgnoreMarker(annotatedMember);
    }
}
  @Test
  public void shouldSerializeAnimals() throws JsonProcessingException {

    ObjectMapper mapper = new ObjectMapper();
    mapper.setAnnotationIntrospector(new JacksonIntrospector());

    DogIgnoreIntrospection dog = new DogIgnoreIntrospection("mammal", 4, "Huski");
    OstrichIgnoreIntrospection ostrich = new OstrichIgnoreIntrospection("oviparous ", 2, 5);

    List<AnimalIgnoreIntrospection> animals = new ArrayList<>();
    animals.add(dog);
    animals.add(ostrich);

    VetIgnoreIntrospection vet = new VetIgnoreIntrospection();
    vet.setAnimalIgnoreIntrospections(animals);
    mapper.writerWithDefaultPrettyPrinter().writeValueAsString(vet);

    System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(vet));

  }
Output:
{
  "animalIgnoreIntrospections" : [ {
    "animalType" : "dog"
  }, {
    "animalType" : "ostrich",
    "age" : 5
  } ]
}

Conclusion

In this article about Serialization and Deserialization in Inheritance with Jackson, we have explored various ways to add and ignore fields in our classes, using annotations and Java classes in an easy and straightforward manner. Jackson is undoubtedly a highly useful library in our applications.

If you wish, you can view the example code on our GitHub repository.

Leave a Reply

Your email address will not be published. Required fields are marked *