2

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
        }
    ]
}
Bar Bokovza
  • 169
  • 1
  • 2
  • 12

2 Answers2

2

This can be done if you annotate your abstract class with something like this (you might need to tweak it to your needs):

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "_type")
@JsonSubTypes({
  @JsonSubTypes.Type(value = Alpha.class, name = "_type"),
  @JsonSubTypes.Type(value = Beta.class, name = "_type"),
})

When you generate the JSON that will insert a _type key with the value of the FQDN class for Alpha, Beta, etc. It should work similar for your use case.

See if this helps for some more background. I was doing something similar a while back – I haven't updated the question though :)

x80486
  • 6,627
  • 5
  • 52
  • 111
  • Besides, you cannot put `Alpha` as a class object in `@JsonSubTypes` due to the fact that Java demands you to do `Alpha.class` – Bar Bokovza Dec 22 '18 at 10:12
  • ...well, that was a typo, it's actually the `Class` what goes in there, so `Alpha.class` is the correct thing. Also, you must have an empty (no args) constructor for this to work. You are using Lombok, so it's easy to forget. – x80486 Dec 22 '18 at 15:44
2

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));
}
Bar Bokovza
  • 169
  • 1
  • 2
  • 12