Search code examples
javajsonjacksonjackson2

Java Jackson Serialize List of Abstract Class


Scenario

Imagine that I have an abstract class named InfoItem, that each class that inherits from it is a different type of Info Type that is used later to help me with the classification.

Each InfoItem have a timestamp as a property (when does this InfoItem arrive )?

Note: I'm using Lombok in this example

@Data
@JsonTypeInfo(use = Id.NAME, visible = true, property = "@type")
public abstract class InfoItem {
    private LocalDateTime timestamp;
}

I have 2 classes that extend this class: Alpha and Beta. Each class has multiple properties that don't have a common property between them:

@JsonTypeName("alpha")
@Data
public class Alpha extends InfoItem {
    private int x;
    private long y;
}

@JsonTypeName("beta")
@Data
public class Beta extends InfoItem {
    private bool isOk;
}

Now imagine that I create a list of InfoItem items:

list[0] = Alpha(x = 10, y = 20)
list[1] = Beta(isOk = false)

Issue

If the list above is saved as a variable named lst, and then I'd run this command:

ObjectMapper mapper = new ObjectMapper();
String s = mapper.writeValueAsString(lst);
System.out.println(s);

I'd get this:

[
    {
        "x": 10,
        "y": 20
    },
    {
        "isOk": false
    }
]

instead of:

[
    {
        "@type": "alpha",
        "x": 10,
        "y": 20
    },
    {
        "@type": "beta",
        "isOk": false
    }
]

It happens when I run the ObjectMapper over a List directly, and not a class that I create and put in it a list.

Case: List in a class that I created

If I create a class named FooBar that have a property lst(type List` - same as above):

@Data
public class FooBar {
    private List<InfoItem> items;
}

and then I do:

FooBar bar = new FooBar();
bar.setItems(lst);

ObjectMapper mapper = new ObjectMapper();
String s = mapper.writeValueAsString(lst);
System.out.println(s);

I'd get:

{
    "items": [
        {
            "@type": "alpha",
            "x": 10,
            "y": 20
        },
        {
            "@type": "beta",
            "isOk": false
        }
    ]
}

Solution

  • I've found after a few days a solution:

    public static <T> Optional<String> toJson(final ObjectMapper mapper, final TypeReference<T> type, final T object) {
        try {
            return Optional.ofNullable(mapper.writer().forType(type).writeValueAsString(object));
        } catch (JsonProcessingException e) {
            return Optional.empty();
        }
    }
    
    public static void main(String[] args) {
        List<InfoItem> items = new ArrayList<>();
        // Set values in items...
    
        ObjectMapper mapper = new ObjectMapper();
        Optional<String> json = toJson(mapper, new TypeReference<List<InfoItem>>() {}, items);
        json.ifPresent(x -> System.out.println(x));
    }