JAX-RS: returns a specific instance of a class from a method with a declared abstract return type

I have a REST service using JAX-RS and Jackson. My client wants to have an abstract class as a return type of service, and I don't know how to get the JAX-RS client to return instances of specific subclasses. Is this possible for representing XML and JSON? If so, evaluate the samples and / or references.

+6
source share
3 answers

There are several ways to achieve this. The easiest way is to use @JsonTypeInfo , as Harry said. All you have to do is annotate your abstract base class with @JsonTypeInfo(use = Id.CLASS, include = As.PROPERTY) and you should be good to go.

Another way is to make sure that all mappers on both the server side and the client side (probably NON_FINAL) default typing are turned on so the type information is (de) serialized on uninstalled classes. Both methods can be improved by providing your own TypeResolverBuilder if you are unsatisfied with the serialization capabilities. More detailed explanations of these approaches can be found in this article or on Working with wiki pages with a Jackson Polymorph manipulator .

Although the @JsonTypeInfo method @JsonTypeInfo simple, getting an actual CXF server / client can be a daunting task. So here is a complete example:

 import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider; import com.fasterxml.jackson.jaxrs.xml.JacksonJaxbXMLProvider; import org.apache.cxf.endpoint.Server; import org.apache.cxf.jaxrs.JAXRSServerFactoryBean; import org.apache.cxf.jaxrs.client.JAXRSClientFactoryBean; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import java.util.Arrays; import java.util.Collections; import java.util.Random; import static javax.ws.rs.core.MediaType.APPLICATION_JSON; import static javax.ws.rs.core.MediaType.APPLICATION_XML; public class FullCxfJaxrsJacksonExample { public static void main(String[] args) { String serverAddress = "http://localhost:9000/"; Server server = null; try { // make server that supports JSON and XML JAXRSServerFactoryBean serverFactory = new JAXRSServerFactoryBean(); serverFactory.setResourceClasses(ShapeServiceRandom.class); serverFactory.setAddress(serverAddress); serverFactory.setProviders(Arrays.asList(new JacksonJaxbJsonProvider(), new JacksonJaxbXMLProvider())); server = serverFactory.create(); // make and use a client JAXRSClientFactoryBean clientFactory = new JAXRSClientFactoryBean(); clientFactory.setAddress(serverAddress); clientFactory.setServiceClass(ShapeService.class); clientFactory.setProvider(new JacksonJaxbJsonProvider()); clientFactory.setHeaders(Collections.singletonMap("Accept", "application/json")); // for an XML client instead of a JSON client, use the following provider/headers instead: //clientFactory.setProvider(new JacksonJaxbXMLProvider()); //clientFactory.setHeaders(Collections.singletonMap("Accept", "application/xml")); ShapeService shapeServiceClient = clientFactory.create(ShapeService.class); for (int i = 0; i < 10; i++) { System.out.format("created shape: %s\n", shapeServiceClient.create()); } } finally { if (server != null) { server.destroy(); } } System.exit(0); } // Put JsonTypeInfo on the abstract base class so class info gets marshalled. // You'll want CLASS instead of MINIMAL_CLASS if your base class and subclasses are in different packages. @JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS, include = JsonTypeInfo.As.PROPERTY) public abstract static class Shape { } public static class Circle extends Shape { public double radius; @Override public String toString() { return "Circle{radius=" + radius + '}'; } } public static class Polygon extends Shape { public int numSides; @Override public String toString() { return "Polygon{numSides=" + numSides + '}'; } } // service definition with abstract return type @Path("/shape") public interface ShapeService { @GET @Path("/create") @Produces({APPLICATION_JSON, APPLICATION_XML}) Shape create(); } // service implementation that returns different concrete subclasses public static class ShapeServiceRandom implements ShapeService { Random random = new Random(); public Shape create() { int num = random.nextInt(8); if (num > 3) { Polygon polygon = new Polygon(); polygon.numSides = num; return polygon; } else { Circle circle = new Circle(); circle.radius = num + 0.5; return circle; } } } } 

This example has been successfully tested using JDK 1.8.0_45 and the following dependencies:

  <dependency> <groupId>com.fasterxml.jackson.jaxrs</groupId> <artifactId>jackson-jaxrs-json-provider</artifactId> <version>2.5.4</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.jaxrs</groupId> <artifactId>jackson-jaxrs-xml-provider</artifactId> <version>2.5.4</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http-jetty</artifactId> <version>3.1.1</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-rs-client</artifactId> <version>3.1.1</version> </dependency> 
0
source

You can try adding annotations JsonTypeInfo and JsonSubTypes

 @JsonTypeInfo(use = Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "type") @JsonSubTypes({ @Type(value = MySubClass.class) }) public abstract class MyAbsClass { .... } public class MySubClass extends MyAbsClass { .... } 

It should add type information to json output.

+2
source

If all you want is to return an arbitrary concrete instance, this "just works" when using a Jackson-based provider. This is the default value for JSON with DropWizard . The only problem that may arise is to use a simple Jersey and JAXB-based provider, which can limit the serialization of an abstract type API.

If you should also be able to deserialize (client-side) to a specific type, then you must use @JsonTypeInfo .

0
source

All Articles