Yahya Cahyadi
Yahya Cahyadi

Reputation: 524

Jackson polymorphism: How to map multiple subtypes to the same class

I'm using Jackson 1.9.x. Sticking with the Animals example, Here's what I'd like to do:

Let's say I have an Animal class:

public class Animal {
    private String type;
    // accessors
}

public class Mammal extends Animal {
    private String diet;
    // accessors
}

public class Bird extends Animal {
    private boolean tropical;
    // accessors
}

I would like to be able to do something like this (where I map a few subtypes to one class, and a few more to a different class):

@JsonTypeInfo(use = Id.NAME, include = As.PROPERTY, property = "type")
@JsonSubTypes({@JsonSubTypes.Type(value = Mammal.class, name = "Dog"),
                @JsonSubTypes.Type(value = Mammal.class, name = "Cat"),
                @JsonSubTypes.Type(value = Bird.class, name = "Dodo"},
                @JsonSubTypes.Type(value = Bird.class, name = "Cockatoo"})
public class Animal {

}

What I'm seeing right now is that Jackson will only recognize the Dog-to-Mammal and the Dodo-to-Bird mapping. This is because StdSubtypeResolver._collectAndResolve() only allows the same class to get registered once (due to the implementation of NamedType.equals()).

Is there a workaround to the issue I'm seeing?

Upvotes: 15

Views: 23816

Answers (5)

Ayhan APAYDIN
Ayhan APAYDIN

Reputation: 1648

As of Jackson 2.12 there is a separate field in @JsonSubTypes.Type annotation.

/**
 * (optional) Logical type names used as the type identifier for the class: used if
 * more than one type name should be associated with the same type.
 *
 * @since 2.12
 */
public String[] names() default {};

Check the example code below.

@JsonTypeInfo(use = Id.NAME, include = As.PROPERTY, property = "type")
@JsonSubTypes({@JsonSubTypes.Type(value = Mammal.class, names = {"Dog", "Cat"}),
                @JsonSubTypes.Type(value = Bird.class, names = {"Dodo", "Cockatoo"}},
                
public class Animal {

}

Upvotes: 1

Ondra Žižka
Ondra Žižka

Reputation: 46796

You may introduce a middle-level abstract classes.

You may also avoid listing the whole list of subtypes and use @JsonTypeName.
This solution is IMHO more ellegant, because

  • the definitions are right next to the actual subtype,
  • long list of subtypes makes the code less maintainable,
  • you can "plug-in" classes on other places.

Howto

By default, Jackson does not traverse the type tree to find about nested subtypes. It would fail with:

Could not resolve type id 'Dodo' as a subtype of
ch.zizka.test.Animal: known type ids = [com.zizka.test.Bird, ... Mammal] ...
at [Source: (byte[])"{

You need to add @JsonSubTypes to the middle-level classes (Bird, Mammal).

The result

  • Animal with @JsonTypeInfo(use=NAME)
    • Mammal with @JsonSubTypes(@Type(Dog.class, Cat.class))
      • Dog with @JsonTypeName("Dog")
      • Cat with @JsonTypeName("Cat")
    • Bird with @JsonSubTypes(@Type(Dodo.class, Cockatoo.class))
      • Dodo with @JsonTypeName("Dodo")
      • Cockatoo with @JsonTypeName("Cockatoo")

Just tested - #WORKSFORME. (Jackson 2.10.5)

Upvotes: 1

erkfel
erkfel

Reputation: 1658

The bug has been resolved in the version 2.6.0, so you just have to update Jackson to version 2.6.0 or later. The additional information is here and here.

Upvotes: 7

hkasera
hkasera

Reputation: 2148

I also faced the same issue and found out that the Subtype mapping expects unique classes.

What I did was to create two classes that extend the same base class. The extended classes are empty as they have the same properties as base class. Then added them to the Subtype map. For example, in your case, it will be -

@JsonTypeInfo(use = Id.NAME, include = As.PROPERTY, property = "type")
@JsonSubTypes({@JsonSubTypes.Type(value = Mammal.class, name = "Dog"),
            @JsonSubTypes.Type(value = Mammal.class, name = "Cat"),
            @JsonSubTypes.Type(value = BirdDodo.class, name = "Dodo"},
            @JsonSubTypes.Type(value = BirdCockatoo.class, name = "Cockatoo"})
public class Animal {

}

public class BirdCockatoo extends Cockatoo{}
public class BirdDodo extends Dodo{}

I understand it is the not the best approach but until the issue is not resolved, it could be the best way to fix this. I followed this approach for now.

Hope it helps you!

Upvotes: 9

StaxMan
StaxMan

Reputation: 116512

Perhaps not by using annotations. Problems comes from the fact that such mapping would not work for serialization, and existing mapping does expect one-to-one (bijection) relationship. But you may want to file an RFE at jackson-databind issue tracker; adding support may be possible.

Upvotes: 4

Related Questions