Java jax-rs client response entity generic - java

I have a problem with a generic function. I want to use a function to which I assign a certain class / type to first generate the corresponding result from a rest response and then return it.
public class TimerService {
[...]
public <T extends myObjInterface> RestMessageContainer<T> send(String endpointPath, Map<String, String> parameters, Class<T> clazz) {
[...]
Response response = webTarget.request(MediaType.APPLICATION_JSON_TYPE).get();
RestMessageContainer<T> container = response.readEntity(new GenericType<RestMessageContainer<T>>() {});
return container;
}
}
public class RestMessageContainer<T extends myObjInterface> {
[...]
#XmlAttribute(name = "data")
private List<T> data;
[...]
}
I get the following error message at runtime.
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.test.myObjInterface` (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
14:47:41,982 ERROR [stderr] (EJB default - 2) at [Source: (org.jboss.resteasy.client.jaxrs.internal.ClientResponse$InputStreamWrapper); line: 3, column: 14] (through reference chain: com.test.RestMessageContainer["data"]->java.util.ArrayList[0])
The error is output for the line RestMessageContainer<T> container = response.readEntity(new GenericType<RestMessageContainer<T>>() {});
Is my approach correct at all or how should I solve my problem?

Thanks for your advice,
I have several subclasses. There is no information about the type in the JSON strings. The type results from the reqeust address. I couldn't configure Jackson to recognize the subtype. There is no unique field in the JSON strings that I could use as a type.
I am not able to change the web service that delivers the JSON strings.
[UPDATE]
I have found a solution. I no longer let the JAX-RS client convert the JSON string. I have the JSON string returned to me as a string and convert it independently using Jackson.
public <T extends myObjInterface> RestMessageContainer<T> send(String endpointPath, Map<String, String> parameters, Class<T> clazz) {
[...]
Response response = webTarget.request(MediaType.APPLICATION_JSON_TYPE).get();
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
RestMessageContainer<T> container = mapper.readValue(response.readEntity(String.class), mapper.getTypeFactory().constructParametricType(RestMessageContainer.class, clazz));
return container;
}

You cannot create an instance of an abstract class. However, you can solve the problem with a simple annotation – #JsonDeserialize on the abstract class:
#JsonDeserialize(as = Cat.class)
abstract class Animal {...}
In your case, the abstract class would be myObjInterface.
Note: If you have more than one subtype of the abstract class, then you should consider including subtype information as shown in this post.

Related

Polymorphic Json Marshalling with Apache Camel

I'm refactoring a camel route to hopefully be a little more generic. (I'm also using spring boot, if that helps for any possible bean injection solutions)
from(fromKafka)
.routeId("Rest Models")
.removeHeaders("*")
.aggregate(new GroupedBodyAggregationStrategy())
.constant(true)
.completionTimeout(batchingInterval)
.process(new ListOfJsonToJsonArray())
.unmarshal().json(JsonLibrary.Jackson, InputArrayPojo.class)
.enrich("seda:rest", mergeRestResult)
the processor ListOfJsonToJsonArray() takes the json string representation of the kafka message, and joins everything, comma separated, with a {[ ]} on the outside.
The InputArrayPojo.class is thus a wrapper for the array of objects that are coming in from kafka. I need to bundle the objects in order to mini-batch to the REST interface in the enrichment. The objects contained are of format InputPojo.class (effectively just a schema, but also performs some basic data quality checks)
I need a way to generify InputPojo.class such that for our new jobs, we can run the same route, but supply a different InputPojo.class.
I've tried to apply polymorphism and create an interface for InputPojo, however this runs into an error when trying to construct the interface.
#JsonSubTypes({
#JsonSubTypes.Type(value=InputPojo.class, name = "online")
})
public interface InputPojoInterface {
}
I also tried some parameterisation, but I had no luck there either because it would not apply the constructor of the bean, none of the methods then existed.
I've also included
com.fasterxml.jackson.databind.exc.InvalidDefinitionException - Cannot construct instance of `InputPojoInterface` (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
at [Source: (ByteArrayInputStream); line: 1, column: 10] (through reference chain: InputArrayPojo["data"]->java.util.ArrayList[0])]
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"data"
})
public class InputArrayPojo{
#JsonProperty("data")
private List<InputPojo> data = null;
#JsonIgnore
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
#JsonProperty("data")
public List<InputPojo> getData() {
return data;
}
#JsonProperty("data")
public void setData(List<InputPojo> data) {
this.data = data;
}
#JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
#JsonAnySetter
public void setAdditionalProperty(String name, Object value) {
this.additionalProperties.put(name, value);
}
}
The enrichment also needs to implement some type of generifying logic
#Override
public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
List<IngestionOutPojo> originalMessages = oldExchange.getIn().getBody(IngestionOutArrayPojo.class).getData();
List<PredictionPojo> enrichmentMessages = newExchange.getIn().getBody(PredictionArrayPojo.class).getData();
List<ModelResultPojo> outputList = new ArrayList<>();
for (int i = 0; i < originalMessages.size(); ++i) {
ModelResultPojo output = new ModelResultPojo();
IngestionOutPojo raw = originalMessages.get(i);
PredictionPojo enrich = enrichmentMessages.get(i);
/*
enrichment logic to create modelResult
*/
outputList.add(modelResult)
}
newExchange.getIn().setBody(outputList);
return newExchange
}
I ended up coming up with a solution by doing the following:
unmarshalled to the default type: Map<String,Object> (without specifying a class, it camel unmarshalls to a Map<String,Object>)
After that I wrote an abstract class that implements a processor. In this processor I take the Map, and apply an abstract editFields() function to the Map.
thus I now have polymorphic handling of business logic through a Map instead of through a POJO.

Implement PATCH endpoint with JAX-RS and Liferay (Apache CXF)

I am trying to implement a PATCH endpoint with JAX-RS in a Liferay OSGi module. The GET, POST and PUT endpoints are working fine, but I am stuck with the PATCH endpoint. As I don't know any better, I am trying to use the example implementation of Daan Scheerens.
My (simplified) implementations so far, beginning with the controller:
#Path("/resources")
public class ResourceController {
#PATCH
#Path("/{resourceId}")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
Response patchResource(#PathParam("resourceId") long resourceId, ObjectPatch objectPatch) {
// Get the resource
Resource resource = getResource(resourceId);
// Apply the patch
objectPatch.apply(resource);
// Return the resource
return Response.ok(resource).build();
}
}
So I need an ObjectPatch interface that I did exactly like in Daan's example:
public interface ObjectPatch {
<T> T apply(T target) throws ObjectPatchException;
}
Next step is to implement the MessageBodyReader:
#Provider
public class PartialJsonObjectPatchReader implements MessageBodyReader<ObjectPatch> {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
#Override
public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return ObjectPatch.class == type && MediaType.APPLICATION_JSON_TYPE.isCompatible(mediaType);
}
#Override
public ObjectPatch readFrom(Class<ObjectPatch> type, Type genericType, Annotation[] annotations,
MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream)
throws IOException {
JsonNode patch = OBJECT_MAPPER.readTree(entityStream);
return new PartialJsonObjectPatch(OBJECT_MAPPER, patch);
}
}
The only difference to the example implementation is that I added the #Provider annotation. As far as I understood this registers the MessageBodyReader implementation automatically to the JAX-RS runtime, like it is described here and here. From the latter:
A class wishing to provide such a service implements the MessageBodyReader interface and may be annotated with #Provider for automatic discovery.
I just have the feeling that this automatic discovery does not happen.
The last important class is the implementation of the ObjectPatch interface:
public class PartialJsonObjectPatch implements ObjectPatch {
private final ObjectMapper objectMapper;
private final JsonNode patch;
public PartialJsonObjectPatch(ObjectMapper objectMapper, JsonNode patch) {
this.objectMapper = objectMapper;
this.patch = patch;
}
#Override
public <T> T apply(T target) throws ObjectPatchException {
ObjectReader reader = objectMapper.readerForUpdating(target);
try {
return reader.readValue(patch);
} catch (IOException e) {
throw new ObjectPatchException(e);
}
}
}
If I now do a PATCH request to the endpoint, it gives me this error message:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of com.example.ObjectPatch (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
I kind of understand the error message as an interface can not be instantiated, but I do not understand why I get this message. Shouldn't it try to instantiate a PartialJsonObjectPatch instead of an ObjectPatch? If I change the parameter class of the patchResource() method to PartialJsonObjectPatch (which I shouldn't), I get this error message:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of com.example.PartialJsonObjectPatch (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
The error messages lead me to this, but adding a default constructor doesn't help.
What am I missing?
The #Provider annotation is not sufficient, I had to add my MessageBodyReader implementation to the singletons in the Application class:
#Override
public Set<Object> getSingletons() {
Set<Object> singletons = new HashSet<>();
// All your other Providers, Readers, Writers, e.g:
singletons.add(new JacksonJaxbJsonProvider());
// My MessageBodyReader implementation
singletons.add(new PartialJsonObjectPatchReader());
return singletons;
}

Java Generics : Object Mapper Converting JSON to Java Object

I would like to deserialize a json string to a java object. I wanted to write a generic method.
public class ObjectSerializer<T> {
T t;
private ObjectMapper mapper = new ObjectMapper();
/* public Person deSerial(String json) throws Exception {
TypeReference<Person> typeRef = new TypeReference<Person>() {};
return mapper.readValue(json, typeRef);
} */
public T deSerialize(String jsonInput) throws Exception {
TypeReference<T> typeRef
= new TypeReference<T>() {};
return mapper.readValue(jsonInput, typeRef);
}
}
When I call deSerialize(validPersonJsonString) [validPersonJsonString : valid person JSON String], it is not working, it it throwing me the error:
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.example.Person.
Whereas, when in call the commented deSerial method, it works fine. Please explain the issue.
Thanks.
Jackson doesn't support TypeReference with generic type parameters because of type erasure. Here's the bug about it.
As far as your use case is concerned, you don't need to use ObjectSerializer class at all. You can directly expose ObjectMapper object to your service layer and perform the deserialization.
If you want to shield the json serializing mechanism from your service layer then you can wrap ObjectMapper implementation into another class (like you have done in your code) but then, accept the class type as a method argument rather than a generic type.

Deserialize JSON to Different types with jacksonObjectMapper

Suppose I got an error from server as JSON which can be in two different structure:
{"errorMessage": "This action is unauthorized."}
OR
{"errorMessage":{"changePassword":"Old password is incorrect."}}
How can I deserialize this kind of json?
What I tried
I tried to have abstract class "Error" and two childs:
abstract class Error() {}
data class SingleError(val errorMessage: String) : Error()
data class MultiError(val errorMessage: Map<String, String>) : Error()
Then I try:
jacksonObjectMapper().readValue<Error>(response.body)
to deserialize, but I haave exception:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.fifth_llc.siply.main.request.Forbidden$Error` (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
at [Source: (String)"{"errorMessage":{"changePassword":"Old password is incorrect."}}"; line: 1, column: 1]
Also I have tried JsonDeserialize annotaiton, but it seem I can use it if I want to parse to concrete type:
#JsonDeserialize(`as` = MultiError::class)
Any help?
Custom deserializer approach:
sealed class Error() {
data class SingleError(val errorMessage: String) : Error()
data class MultiError(val errorMessage: Map<String, String>) : Error()
}
...
class ErrorDeserializer : StdDeserializer<Error>(Error::class.java) {
companion object {
private val MAP_TYPE_REFERENCE = object : TypeReference<Map<String, String>>() {}
}
#Throws(IOException::class, JsonProcessingException::class)
override fun deserialize(jp: JsonParser, ctxt: DeserializationContext): Error {
val mapper = jp.codec as ObjectMapper
val node: JsonNode = mapper.readTree(jp)
val msgNode = node.get("errorMessage")
if (msgNode.isValueNode) {
val errorMsg = msgNode.asText()
return Error.SingleError(errorMsg)
} else {
val errorMsgs = mapper.readValue<Map<String, String>>(msgNode.toString(),
MAP_TYPE_REFERENCE)
return Error.MultiError(errorMsgs)
}
}
}
Usage:
val mapper = ObjectMapper()
val module = SimpleModule().addDeserializer(Error::class.java, ErrorDeserializer())
mapper.registerModule(module)
val error = mapper.readValue<Error>("json content", Error::class.java)
when (error) {
is Error.SingleError -> {
// error.errorMessage
}
is Error.MultiError -> {
// error.errorMessage
}
}
As far as I know, at least I experienced, you can not assign abstract type to readValue method, you have to specified it's concrete type both in readValue parameterizedType and it's properties.
jacksonObjectMapper().readValue<Error>(response.body)
In the line above you have to replace Error by one of it's subtype you wish and in following you must replace Map bye HashedMap
data class MultiError(val errorMessage: Map<String, String>) : Error()
Another way is using #JsonTypeInfo into json object to notify JackSon to include exact object type as meta-data into json stream. this solution needs code correction in both server and client side application. Refer to following link:
https://fasterxml.github.io/jackson-annotations/javadoc/2.4/com/fasterxml/jackson/annotation/JsonTypeInfo.html
The problem is that Jackson either needs a JsonCreator or an empty constructor.
Data classes don't have empty constructors. If you want an empty constructor anyway, you can use the Kotlin No-Args plugin.
gradle:
dependencies {
classpath "org.jetbrains.kotlin:kotlin-noarg:$kotlinVersion"
...
}
...
apply plugin: "kotlin-noarg"
...
noArg {
annotation("com.your.package.NoArgs")
}
Now, create the Custom Annotation:
package com.your.package
annotation class NoArgs
Now the easiest * way is to use a generic type as your error message and use the custom annotation:
#NoArgs
data class Error<out T>(val errorMessage: T)
val x = ObjectMapper().readValue<Error<*>>(json1)
val y = ObjectMapper().readValue<Error<*>>(json2)
when(y.errorMessage) {
is String -> println("I'm a String!")
is Map<*,*> -> println("I'm a Map")
else -> println("I'm something else!")
}
You could also define the type explicitly on readValue, if you know it in advance: readValue<Error<String>>
You could also create a method inside Error for that logic in when
* There might be more elegant or useful solutions depending on your exact context.

Remove "type" from JSON output jersey moxy

How to remove the type from the JSON output that I have. I have a class/bean that contains output of a REST service.I'm using jersey-media-moxy to do the conversion.
The service
#Resource
public interface MyBeanResource
{
#GET
#Path("/example")
#Produces( MediaType.APPLICATION_JSON )
public Bean getBean();
}
The Bean
#XmlRootElement
class Bean
{
String a;
}
I want to add some functionality (for initializing the bean using the constructor)
class BeanImpl extends Bean
{
BeanImpl(OtherClass c)
{
a = c.toString()
}
}
The outputted JSON is:
{type:"beanImpl", a:"somevalue"}
I do not want the type in my JSON. How can I configure this?
I get the same error when I extend a class and generate JSON -- but only for a top-level (root) class. As a workaround, I annotate my subclass with #XmlType(name=""), which prevents the generated type property from appearing in my JSON.
Blaise, I'm not sure why this works. Any thoughts?
MOXy will add a type indicator to differentiate between the different subtypes. This however will only happen if MOXy is aware of the subtypes (it isn't by default).
Demo Code
Demo
Below is the equivalent code that Jersey will call on MOXy.
import org.eclipse.persistence.jaxb.rs.MOXyJsonProvider;
public class Demo {
public static void main(String[] args) throws Exception {
MOXyJsonProvider mjp = new MOXyJsonProvider();
BeanImpl beanImpl = new BeanImpl(new OtherClass());
mjp.writeTo(beanImpl, Bean.class, Bean.class, null, null, null, System.out);
}
}
Output
{}
Possible Problem?
Do you potentially have an #XmlSeeAlso annotation on your real Bean class?
import javax.xml.bind.annotation.*;
#XmlRootElement
#XmlSeeAlso(BeanImpl.class)
class Bean
{
String a;
}
Then the output will be (assuming BeanImpl also has a no-arg constructor):
{"type":"beanImpl"}
You can build a custom message body writer .
#Provider
#Produces({
MediaType.APPLICATION_JSON
})
public class BeanBodyWriter implements MessageBodyWriter<Bean> {
#Override
public long getSize(Bean t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
// Called before isWriteable by Jersey. Return -1 if you don't the size yet.
return -1;
}
#Override
public boolean isWriteable(Class<?> clazz, Type genericType, Annotation[] annotations, MediaType mediaType) {
// Check that the passed class by Jersey can be handled by our message body writer
return Bean.class.isAssignableFrom(clazz);
}
#Override
public void writeTo(Bean t, Class<?> clazz, Type genericType, Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders, OutputStream out) throws IOException, WebApplicationException {
// Call your favorite JSON library to generate the JSON code and remove the unwanted fields...
String json = "...";
out.write(json.getBytes("UTF-8"));
}
}
Use this to generate JSON and you won't have that problem:
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>2.3.3</version>
</dependency>
I used a Jersey specific Jackson package in a slightly different case, it worked. Detailed configuration is described in Jersy document. In my case, I used a generic type field in an #XmlRootElement class. MOXy added a type in the JSON output. I can see why MOXy does it. If the JSON output needs to be unmarshalled back to a Java object, MOXy needs to know the type to create the correct object. However, in my case, the type is unsightly in the JSON output.

Categories

Resources