3
votes

I'm trying to deserialize a fairly complex JSON structure using Jackson.

Serializing works fine:

Param interface:

public interface Param<T> {
}

Not.java:

public class Not implements Param<Param<?>> {

    private Param not;

    public Not(){

    }

    public Not(Param not) {
        this.not = not;
    }

    public Param getNot() {
        return not;
    }

}

And.java:

public class And implements Param<List<?>> {

    private List<Param> and;

    public List<Param> getAnd() {
        return and;
    }

    public List<Param> add(Param ... params){
        for (Param param : params){
            this.and.add(param);
        }
        return this.and;
    }

    public And() {
        this.and = new ArrayList<>();
    }

    public And(Param ... params){
        this.and = new ArrayList<>();
        for (Param param : params){
            this.and.add(param);
        }
    }

}

Company.java:

public class CompanyName implements Param<String> {

    private String companyName;

    public CompanyName(String value) {
        this.companyName = value;
    }

    public String getCompanyName() {
        return companyName;
    }
}

Serializing:

ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.writeValueAsString(new And(new Or(new CompanyName("ABC"), new CompanyName("DEF")), new Not(new CompanyName("GHI")))));

Prints:

{"and":[{"or":[{"companyName":"ABC"},{"companyName":"DEF"}]},{"not":{"companyName":"GHI"}}]}

Now deserializing, how does Jackson know how to map and / or / companyName / not back to their objects?

And and = mapper.readValue(json, And.class);

Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of com.ahp.messaging.param.Param, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information at [Source: {"and":[{"or":[{"companyName":"ABC"},{"companyName":"DEF"}]},{"not":{"companyName":"GHI"}}]}; line: 1, column: 9] (through reference chain: com.ahp.messaging.param.And["and"]->java.util.ArrayList[0])

To get rid of the exception, I modified the Param interface as follow:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "T", visible = false)
@JsonSubTypes({
    @JsonSubTypes.Type(value = Not.class, name = "not"),
    @JsonSubTypes.Type(value = And.class, name = "and"),
    @JsonSubTypes.Type(value = Or.class, name = "or"),
    @JsonSubTypes.Type(value = CompanyName.class, name = "companyName")

})
public interface Param<T> {

}

Now it serializes to this:

{"T":"and","and":[{"T":"or","or":[{"T":"companyName","companyName":"ABC"},{"T":"companyName","companyName":"DEF"}]},{"T":"not","not":{"T":"companyName","companyName":"GHI"}}]}

which deserializes perfectly, but there's type information on everything, is there a way to get rid of the type information and only have it where it's really needed?

1
Do you mean the T at the root? All the others seem necessary. - Sotirios Delimanolis
I'd prefer to get rid of all of them, but that probably requires a custom deserializer which I've never done before. So if they are all required, then then I'll accept as correct answer a sample that can get going in the right direction with a custom deserializer. - Jan Vladimir Mostert

1 Answers

0
votes

Short answer, the type information is needed.

With XML you get the type information with every element, with JSON, you sacrifice the type information for smaller representations, but in this instance, scrapping the type information means there's no way to infer what class it should map to.