1

I have a base class and two sublcasses:

public /*abstract*/ class TargetPoi {
    private String poiId;
    ...
}

public class TargetSub1Poi extends TargetPoi {
    private String oneMoreId;
    ...
}

public class TargetSub2Poi extends TargetPoi {
    ...
}

Is it possible to declare the base class abstract? ...I always get an exception when a JSON is send with the request if I use the abstract keyword...

Exception Description: This class does not define a public default constructor, or the constructor raised an exception.
Internal Exception: java.lang.InstantiationException
Descriptor: XMLDescriptor(com.foo.bar.TargetPoi --> [])

When the POST request with its JSON in the request body is coming into the Jersy Resource I want to deserialize the JSON into the proper TargetPoi subclasses. The JSON:

{
"requestId": "84137f1ab38f4bf585d13984fc07c621",
"startTime": "2013-10-30T18:30:00+02:00",
"endTime": "2013-10-30T18:45:00+02:00",
"targetPoi": 
{
    "poiId": "0000000602",
    "oneMoreId": "1"
},
"type": "Block",
"notification": true
}

My Resource hast a method defined this way...

@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public Response doReservation(final ReservationDO reservationDO
        , @QueryParam("serviceType") final String serviceType) {
...
}

The JSON shall be deserialized in this class:

@XmlRootElement
public class ReservationDO<T extends TargetPoi>
{
    public String           requestId;
    public String           startTime;
    public String           endTime;
    public String           serviceType;
    public T                targetPoi;
    ...
}

How can I tell Jackson to bind the JSON for the targetPoi properly to the correct subtype (TargetSub1Poi)? The serviceType could tell me to which subtype the targetPoi is to bind to...but I think this information can't be used from Jackson, does it? When I print out the deserialized JSON in th edoreservation method the oneMoreId part coming with the original JSON is lost.

Do I have to provide any TypeInfo or can I achieve it without?

du-it
  • 2,561
  • 8
  • 42
  • 80

1 Answers1

0

It is possible to declare your parent class abstract and have a generic field. You just need to tell Jackson which sub-type to use to create an instance of you object. It can be done by using @JsonTypeInfo annotation as described at the Jackson polymorphic deserialization wiki page.

Here is an example:

public class JacksonPoi {
    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
            include = JsonTypeInfo.As.PROPERTY, property = "type")
    public static abstract class TargetPoi {
        public String poiId;
    }

    @JsonTypeName("sub1")
    public static class TargetSub1Poi extends TargetPoi {
        public String oneMoreId;

        @Override
        public String toString() {
            return "TargetSub1Poi{" +
                    "poiId='" + poiId + '\'' +
                    "oneMoreId='" + oneMoreId + '\'' +
                    '}';
        }
    }

    @JsonTypeName("sub2")
    public static class TargetSub2Poi extends TargetPoi {
        public String twoMoreId;

        @Override
        public String toString() {
            return "TargetSub2Poi{" +
                    "poiId='" + poiId + '\'' +
                    "twoMoreId='" + twoMoreId + '\'' +
                    '}';
        }
    }

    public static class  Bean<T extends TargetPoi> {
        public String field;
        public T poi;

        @Override
        public String toString() {
            return "Bean{" +
                    "field='" + field + '\'' +
                    ", poi=" + poi +
                    '}';
        }
    }

    public static final String JSON = "{\n" +
            "\"poi\": \n" +
            "{\n" +
            "    \"type\": \"sub1\",\n" +
            "    \"poiId\": \"0000000602\",\n" +
            "    \"oneMoreId\": \"1\"\n" +
            "},\n" +
            "\"field\": \"value1\"\n" +
            "}";
    public static final String JSON2 = "{\n" +
            "\"poi\": \n" +
            "{\n" +
            "    \"type\": \"sub2\",\n" +
            "    \"poiId\": \"0000000602\",\n" +
            "    \"twoMoreId\": \"13\"\n" +
            "},\n" +
            "\"field\": \"value2\"\n" +
            "}";

    public static void main(String[] args) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerSubtypes(TargetSub1Poi.class, TargetSub2Poi.class);
        Bean<TargetSub1Poi> poi1 = mapper.readValue(JSON, new TypeReference<Bean<TargetSub1Poi>>() {});
        System.out.println(poi1);
        Bean<TargetSub2Poi> poi2 = mapper.readValue(JSON2, new TypeReference<Bean<TargetSub2Poi>>() {});
        System.out.println(poi2);
    }
}

Output:

Bean{field='value1', poi=TargetSub1Poi{poiId='0000000602'oneMoreId='1'}}
Bean{field='value2', poi=TargetSub2Poi{poiId='0000000602'twoMoreId='13'}}
Alexey Gavrilov
  • 10,593
  • 2
  • 38
  • 48
  • Yes, I read about it. All those soultions do require additional type information in the incoming JSON, don't they? Of course...how else should the mapper know to which class the JSON has to be mapped to. – du-it Jun 19 '14 at 10:42
  • Yep. You may find interesting [this answer](http://stackoverflow.com/a/24273269/3537858) from the Jackson creator (I think it is him), which discusses a little the alternatives. – Alexey Gavrilov Jun 19 '14 at 10:49