8

My supertype is annotated with

@JsonTypeInfo(use = Id.NAME, include = As.PROPERTY, property = "_typeid")

-so that serialised subtypes include the extra field _typeid containing the name of the subtype. This is intentional and necessary for my app.

But in a particular context I would like to export them as "pure" JSON, i.e. without the _typeid metadata field.

Is there any way of making an ObjectMapper ignore the @JsonTypeinfo annotation during serialisation? I can't find any relevant config- or feature settings. Would I have to resort to postfiltering or alternative serialisers?

I know that removing or altering the annotations would do it, but this is not an option in this case.

ObjectMapper().configure(MapperFeature.USE_ANNOTATIONS, false);

-will turn off all annotations. This does remove the offending field, but also kills other annotations that I would like to work.

Joachim Lous
  • 1,316
  • 1
  • 14
  • 22
  • 1
    reading https://stackoverflow.com/questions/31680046/how-to-ignore-pojo-annotations-while-using-jackson-objectmapper might help. But apparently it will disable all annotations, not just that one – Wisthler Feb 15 '19 at 12:03
  • Thanks, I was not aware of that one. But alas, I do have other annotations I want to keep, like @JsonInclude(Include.NON_NULL) – Joachim Lous Feb 15 '19 at 12:07
  • I was afraid of that. Might be a good idea to dig dipper in that part of code to see if there is a way to disable only part of it – Wisthler Feb 15 '19 at 12:09

2 Answers2

6

You can add/remove annotation in runtime using JsonView annotation. Let's assume that we have one abstract class Base and one implementation Base1. Instead of adding annotations to Base directly we can do that by adding new interface with these annotations. See below example:

abstract class Base {
}

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "_typeid")
@JsonSubTypes({
        @JsonSubTypes.Type(name = "base1", value = Base1.class)
})
interface BaseTypeView {
}

class Base1 extends Base {
    private String value = "Base 1";

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }
}

Now, we need to create two ObjectMappers: one wich uses this view and another which does not.

import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;

public class JsonApp {

    public static void main(String[] args) throws Exception {
        ObjectMapper mapper = createObjectMapper();
        ObjectMapper mapperWithView = createObjectMapper();
        mapperWithView.addMixIn(Base.class, BaseTypeView.class);

        System.out.println(mapper.writeValueAsString(new Base1()));
        System.out.println(mapperWithView.writeValueAsString(new Base1()));
    }

    private static ObjectMapper createObjectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.enable(SerializationFeature.INDENT_OUTPUT);
        return mapper;
    }
}

Above code prints:

{
  "value" : "Base 1"
}
{
  "_typeid" : "base1",
  "value" : "Base 1"
}
Michał Ziober
  • 37,175
  • 18
  • 99
  • 146
3

Based on Michal's answer, this simplified version is pretty close to what I'm looking for:

import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.ObjectMapper;

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
@JsonSubTypes({
    @JsonSubTypes.Type(value = Cat.class)
})
class Animal { }

class Cat extends Animal {
    public final String genus = "felis";
}

public class Example {
    public static void main(String[] args) throws Exception {
        Cat bill = new Cat();

        ObjectMapper typed = new ObjectMapper();
        System.out.println(typed.writeValueAsString(bill));

        @JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
        class NoTypes { }

        ObjectMapper untyped = new ObjectMapper().addMixIn(Animal.class, NoTypes.class);
        System.out.println(untyped.writeValueAsString(bill));
    }
}

This will output

{"@type":"Cat","genus":"felis"}
{"genus":"felis"}

This approach has the advantages that it requires no control over the data classes - everything can be done locally while configuring the mapper - and that the mixin is general and can be applied to any base class.

It does still require explicit configuration for every targeted base class, though, so it is not quite a general solution.

Joachim Lous
  • 1,316
  • 1
  • 14
  • 22
  • `@JsonTypeInfo(use = Id.NAME, include = As.EXISTING_PROPERTY)` seems to do the job without additional configuration. – Guillaume F. Oct 21 '21 at 13:30
  • @GuillaumeF. "The job" in the original question was to treat the data classes as given, including JsonTypeInfo, but override serialising behaviour for only a special case. Hence "this is not an option in this case". – Joachim Lous Jan 17 '22 at 16:29