How do I ignore a class that's within a list from being serialized using Jackson?

Viewed 315

I'm currently using Jackson to serialize a big class in my program, but I can't seem to find how to ignore/skip specific classes from being serialized. I've tried using the annotation @JsonIgnoreType like it was recommended here, but that only seems to work for actual fields (not entries in a list). I've also tried tagging the classes I want to ignore with @JsonIgnoreType and using a custom annotation introspector, but it just saves the empty classes without the fields (except for className):

saveMapper.setAnnotationIntrospector(new JacksonAnnotationIntrospector() {
    @Override
    public boolean hasIgnoreMarker(AnnotatedMember m) {
        return super.hasIgnoreMarker(m) || m.getDeclaringClass().getAnnotation(JsonIgnoreType.class) != null;
    }
});

...

@JsonIgnoreType
private static class ScriptA extends Script {
    private int dontKeep;

    public ScriptA() {
        dontKeep = 25;
    }
}

Result:

{
    "scripts" : [ {
        "className" : "com.JsonExemple$ScriptA"
    }, {
        "className" : "com.JsonExemple$ScriptB",
        "keep" : 50
    } ]
}

Here's the snippet:

package com;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.SerializationFeature;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

/**
 * @author clem585
 * @created 28/02/2021 - 1:43 AM
 */
public class JsonExemple {

    public static void main(String[] args) {
        try {
            // create Jackson JSON writer
            ObjectMapper saveMapper = new ObjectMapper()
                    .configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);

            ObjectWriter saveWriter = saveMapper.writer().withDefaultPrettyPrinter();

            // instantiate class I want to save
            ExempleClass exempleClass = new ExempleClass();

            // create empty file
            File file = new File("D:/Desktop/exemple.json");
            new File("D:/Desktop/").mkdirs();
            file.createNewFile();

            saveWriter.writeValue(file, exempleClass);
            System.out.println("Saved exemple.json on the desktop of the D drive");
        } catch (Exception e) {
            System.out.println("Exception attempting to save exemple");
            e.printStackTrace();
        }
    }

    private static class ScriptB extends Script {
        @JsonProperty
        private int keep;

        public ScriptB() {
            keep = 50;
        }
    }

    // ignore this class
    private static class ScriptA extends Script {
        private int dontKeep;

        public ScriptA() {
            dontKeep = 25;
        }
    }

    @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "className")
    private static abstract class Script {

    }

    private static class ExempleClass {
        @JsonProperty
        private List<Script> scripts;

        public ExempleClass() {
            scripts = new ArrayList<>();
            scripts.add(new ScriptA());
            scripts.add(new ScriptB());
        }
    }

}
1 Answers

Jackson seems to be a bit stubborn when it comes to filter lists by a certain type. @JsonIgnoreType in ScriptA would work if you have a property of that type in ExempleClass, but not when it is a possible sub-type in a list.

So you need a workaround instead. One would a custom serializer for ExempleClass, or you filter the scripts prior serializing them to JSON:

private static class ExempleClass {
    private List<Script> scripts;

    public ExempleClass() {
        scripts = new ArrayList<>();
        scripts.add(new ScriptA());
        scripts.add(new ScriptB());
    }

    @JsonProperty("scripts")
    private List<Script> filterScriptsForJson() {
        return scripts.stream()
                .filter(s -> !(s instanceof ScriptA))
                .collect(Collectors.toList());
    }
}

As you can see, this code doesn't annotate the property directly, but uses a private "getter" instead. Jackson will use that method to serialize the property named "scripts" (you can use any name here, but we want to imitate the actual "scripts" property), so that method can filter the list to remove all ScriptA instances. It is also marked as private, because we don't want any other classes to use that method, it exists only for Jackson.

If you use the class ScriptA somewhere else in your code, then you should still try to use @JsonIgnoreType and @JsonIgnore annotations whenever possible and only use the above mentioned workaround, when those annotations fail.

Related