Spring Boot Jackson defaultImpl with JsonTypeInfo - java

I am trying to handle all types of Jackson exceptions that turn up during REST API requests in a Spring Boot application. If something cannot be serialized, JsonMappingException is thrown. I handle this exception, build the field path that cannot be serialized (using exception.getPath) and return this information.
Now, I have some classes that implement the same interface (polymorphism) and have to work with them during a request. This means I also expose them to the REST API and can be included in the request/response body. Here is the interface:
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXISTING_PROPERTY,
property = "service",
defaultImpl = DefaultNotificatorPresentation.class,
visible = true
)
#JsonSubTypes({
#JsonSubTypes.Type(value = EmailNotificatorPresentation.class, name = "email")
})
public interface NotificatorPresentation {
String getService();
}
Basically, there are different types of notificators which all have different service (email, sms, etc). This property is used for the #JsonTypeInfo. Everything works as expected until I started testing if JsonMappingException is thrown correctly with the JSON subtypes.
JsonMappingException is thrown for all properties (eg. when malformed) and InvalidTypeIdException when service is not any of the available types (only email at the moment). I would like to tell the user the available options for the service property (when string is given but does not match the available types - email, sms, etc) and that it is malformed when no string is provided (object or array for example).
I came up with a solution that uses defaultImpl of #JsonTypeInfo and uses a custom class with custom validation annotation and ConstraintValidator that handles it.
public class DefaultNotificatorPresentation implements NotificatorPresentation {
// implementation of getService() and validation annotation
}
The annotation has a default message - available services are only email, sms. That way, every time the default implementation is created (always when an invalid service is given by the user) there will be a validation error. This approach works when the property service in the json request is of type string - "not found service" for example.
But when object ({ "example": true }) is set to the service property, the defaultImpl class is created twice. The first instance is given property service "{" (the first character of { "example": true }). The second one service is just null. This creates 2 validation exceptions but must throw JsonMappingException.
Do you have any ideas on how this can be solved? I can even use a totally different approach that handles Jackson polymorphism.

Related

Ktorm entities as springboot controller parameters

I'm trying to use Ktorm in my new springboot application, and get myself into problem when trying to use Ktorm entities interfaces as springboot controller parameters.
The entity and Controller look like this:
// Controller definition
#RestController
class TaskController() {
#PostMapping
fun addTask(
#RequestBody task: Task
): Long {
// ... do something with `task`
}
}
// Entity definition (unncessary properties are omitted)
interface Task : Entity<Task> {
var id: Long
var title: String
var content: String
companion object : Entity.Factory<Task>()
}
I got this exception once calling function addTask():
[HttpMessageConversionException]
Type definition error: [simple type, class website.smsqo.entity.Task]; nested exception is:
[com.fasterxml.jackson.databind.exc.InvalidDefinitionException]
Cannot construct instance of website.smsqo.entity.Task (no Creators, like default constructor, exist):
abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information\n at [Source: (PushbackInputStream); line: 1, column: 1]"
}
(Paramter task is posted from front-end by RequestBody)
I think maybe the reason is that, as an interface, springboot can't find a proper way to initialize Task. However, refactoring it into this is surely not an elegant solution:
#RestController
class TaskController() {
#PostMapping
fun addTask(
id: Long, title: String, content: String // even more fields...
): Long {
val task = Task {
this.id = id
this.title = title
this.content = content
}
// ... do something with `task`
}
}
Any better solution proposed? Thanks for your reply in advance!
Well, it turns out that solution was noted explicitly in documents provided by Ktorm:
// extracted from org.ktorm.entity.Entity
/*
* Besides of JDK serialization, the ktorm-jackson module also supports serializing entities in JSON format. This
* module provides an extension for Jackson, the famous JSON framework in Java word. It supports serializing entity
* objects into JSON format and parsing JSONs as entity objects. More details can be found in its documentation.
*/
Implementing org.ktorm:ktorm-jackson:3.4.1 brings us a Jackson Module, named KtormModule in package org.ktorm.jackson. What we need to do next is applying the module to our springboot application as in class annotated by #Configuration:
#Configuration
class KtormConfig {
#Bean
fun ktormJacksonModule(): Module = KtormModule()
// ... and other configurations if you like
}
And that's it. Such KtormModule will be discovered and applied by jackson on springboot application launches, after which there's no more problem encoding and decoding between json and Ktorm Entities.

Mapping JSON response to an abstract type in JAVA rest Service

I am working with an ecommerce company and I am integrating with 3 different payment gateways. All of them require a callbackurl to post back the transaction status.
I have defined a resource to store the status of the transaction
http://www.api.com/api/users/{userid}/order/{orderId}/payments/{paymentModeId}/paymentStatus
I have defined an interface called IPaymentStatusResponse and have created 3 implementations. Based on the paymentModeId in the uri path, appropriate implementation will be picked up to persist the transaction status.
eg: callback url for three different gatewyas will look like this
payment mode 1 - paytm, payment mode 2 - payu ,payment mode 3- cc avenue.
http://www.api.com/api/users/300/order/501/payments/1/paymentStatus
http://www.api.com/api/users/300/order/501/payments/2/paymentStatus
http://www.api.com/api/users/300/order/501/payments/3/paymentStatus
Method signature
public void createPaymentStatus(
#PathParam("paymentModeId") int paymentModeId,
IPaymentStatusResponse response) throws MyAppException {
paymentServiceImpl.createPaymentResponse(response, paymentModeId);
}
Is this the correct way to approach this?
When I did a HTTP post I get the following error:
Can not construct instance of com.myapp.dto.payments.IPaymentStatusResponse, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information
at [Source: org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$UnCloseableInputStream#1ee3ab21; line: 1, column: 1]
Other option , I ll have to define different end points for all the different gateways and map the response object.
UPDATE:
A good explanation can be found here
http://programmerbruce.blogspot.in/2011/05/deserialize-json-with-jackson-into.html
It needs a type element in the response json to pick the concrete class.
Sample json with type mentioned and configuration of my interface. This works. But not sure how to handle this because the response json is not under my control and it comes from the payment gateway providers
{
"MID":"abc",
"TXNID":"T123",
"ORDERID":"100",
"BANKTXNID":"B123",
"TXNAMOUNT":"1",
"CURRENCY":"INR",
"STATUS":"TXN_SUCCESS",
"RESPCODE":"01",
"RESPMSG":"Txn Success",
"TXNDATE":"2015-12-14 02:10:29.742447",
"GATEWAYNAME":"ICICI",
"BANKNAME":"ICICI",
"PAYMENTMODE":"CC",
"type":"PayTMPaymentResponse",
"CHECKSUMHASH":"ggg"
}
Interface
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
#JsonSubTypes({ #Type(value = PayTMPaymentResponse.class, name = "PayTMPaymentResponse") })
public interface IPaymentStatusResponse {
}
Can this be implemented with some query or path parameter ?
Spring/Jackson is not able to deserialize automatically.
You'll have to provide custom deserializer for a given type.
I think it should be easier to do one POJO for shared information and 3 strategies.
it should help in case if you're using jackson. I think other libs have something similar to that.
http://wiki.fasterxml.com/JacksonPolymorphicDeserialization

Spring Boot serialize text/javascript to JSON

I created the following Kotlin data class:
#JsonInclude(JsonInclude.Include.NON_NULL)
public data class ITunesArtist(val artistName: String,
val artistId: Long, val artistLinkUrl: URL)
(a data class is a Kotlin class that auto-generates equals, hashcode, toString etc at compile time - saves time).
Now I've tried populating it using Spring RestTemplate:
#Test
fun loadArtist()
{
val restTemplate = RestTemplate()
val artist = restTemplate.getForObject(
"https://itunes.apple.com/search?term=howlin+wolf&entity=allArtist&limit=1", ITunesQueryResults::class.java);
println("Got artist: $artist")
}
It fails with:
Could not extract response: no suitable HttpMessageConverter found for response type
[class vampr.api.service.authorization.facebook.ITunesArtist]
and content type [text/javascript;charset=utf-8]
Fair enough - the JSON object mapper was probably expecting mime-type of text/json. Other than telling RestTemplate to map to String::class.java, and then instantiating an instance of JacksonObjectMapper by hand, is there a way to tell my RestTemplate to treat the returned mime type as JSON?
Instead of providing defaults for all properties in your data class you can also use this: https://github.com/FasterXML/jackson-module-kotlin
This Jackson module will allow you to serialize and deserialize Kotlin's data classes without having to worry about providing an empty constructor.
In a Spring Boot Application you can register the module with a #Configuration class like so:
#Configuration
class KotlinModuleConfiguration {
#Bean
fun kotlinModule(): KotlinModule {
return KotlinModule()
}
}
Other than that you can also use the extension functions mentioned in the documentation to register the module with Jackson.
Besides supporting data classes you will also get support for several classes from the Kotlin stdlib, like Pair for example.
Not sure about Spring, but Jackson needed me to specify that I worked with a Java Bean. You see, Kotlin data class is exactly the same as a standard Bean on the byte code level.
Do not forget that Java Bean specification implies an empty constructor (without parameters). A nice way to have it auto-generated is to provide default values for all parameters of your primary constructor.
To serialize an object from Jackson to String:
The 'get' portion of Java Beans specification is required.
To read a JSON string to object:
The 'set' portion of the spec is required.
Additionally the object requires an empty constructor.
Modify the class to include:
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonInclude(JsonInclude.Include.NON_NULL)
data public class ITunesArtist(var artistName: String? = null,
var artistId: Long = -1L, val amgArtistId: String = "id",
var artistLinkUrl: URL? = null)
Fields provide defaults in order for there to be an empty constructor.
Edit:
Uses the Kotlin Module from #mhlz's (now accepted) answer removes the need to provide a default constructor.

Use Json root element only for some classes

I'm using dropwizard to create REST API. But I dont understand, how can I configure Jackson to exclude some classes from WRAP_ROOT_VALUE/UNWRAP_ROOT_VALUE features? Right now I get a post request with json body that doesn't include root element name:
{
"identification": "dummyuser",
"password":"dummypass"
}
This should map to java class LoginRequest:
public class LoginRequest {
public String identidication;
public String passwrd;
}
I also get requests for some types that include root element name:
{
"user":{
"id":12345,
"name":"John Doe"
}
}
This should be mapped to:
#JsonRootName("user")
public class User {
...
}
To get root element working I had to include:
environment.getObjectMapper().configure(SerializationFeature.WRAP_ROOT_VALUE, true);
environment.getObjectMapper().configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
but now it applies for all classes. This means that whenever login request comes in, server will throw an error because it expects to see root element name.
Use JsonTypeName with JsonTypeInfo instead of JsonRootName:
#JsonTypeName("user")
#JsonTypeInfo(include= JsonTypeInfo.As.WRAPPER_OBJECT,use= JsonTypeInfo.Id.NAME)
public class User {
...
}
#JsonTypeName
Annotation used for binding logical name that the annotated class has. Used with JsonTypeInfo (and specifically its JsonTypeInfo.use() property) to establish relationship between type names and types.
#JsonTypeInfo
Annotation used for configuring details of if and how type information is used with JSON serialization and deserialization, to preserve information about actual class of Object instances. This is necessarily for polymorphic types, and may also be needed to link abstract declared types and matching concrete implementation.

Jackson - Required property?

I'm using Jackson's readValue() method on an object mapper to read from a JSON file and convert it into my java object.
eg.
mapperObject.readValue( node, MyTargetClass.class )
Are there any annotations that I can set on MyTargetClass to enforce required attributes? For example, if I have a JSON object with properties ABC,DEF and GHI, and my Json is the following
{
"ABC" : "somevalue"
"DEF" : "someothervalue"
}
I want it to fail somehow, and only succeed on the readValue if it contained ABC, DEF and GHI.
You can mark a property as required with the #JsonProperty(required = true) annotation, and it will throw a JsonMappingException during deserialization if the property is missing or null.
Edit: I received a downvote for this without comment. I'd love to know why, since it does exactly the right thing.
Jackson does not include validation functionality, and this is by design (i.e. that is considered out-of-scope). But what is usually used is Bean Validation API implementation.
The nice thing about this is decoupling between data format handling, and validation logic.
This is what frameworks like DropWizard use; and it's the direction JAX-RS (like Jersey) are taking things for JAX-RS 2.0.
If you want to make sure a json field is provided, you have to use the #JsonProperty(value = "fieldName", required = true) annotation as a parameter to the constructor. But this is not enough, also the Constructor should have #JsonCreator annotation.
For example, if you have a field named 'endPoint' and you want o make sure it is provided in the JSON file, then the following code will throw an exception if it is not provided.
#JsonCreator
public QuerySettings(#JsonProperty(value = "endPoint", required = true) String endPoint) {
this.endPoint = endPoint;
}
I found this link helpful to understand the Jackson annotations. It also well explains why required=true is not enough and counter-intuitive to its name.
If you are neither satisfied with using #JsonProperty(required = true) as it works only with #JsonCreator nor with the use of bean validation then one more way of tackling it would be to catch this in your setter methods for the relevant variables.
You can simply check if the variable is null before setting it and throw an IllegalArgumentException or NullPointerException (as preferred by few people)
Note: It depends on how your POJO is defined too, so please make sure that it is going the setter method route for this solution to work.

Categories