Is it possible to create a custom dozer initializer so it accepts a parameter with the constructor and not just an empty constructor?
For example, next converter failed because of dozer can't initialize it, and throws java.lang.InstantiationException
public class MyCustomDozerConverter extends DozerConverter<MyObject, String> {
private static String myParameter;
// How could dozer accepts this constructor?
public MyCustomDozerConverter(String myParameter) {
super(MyObject.class, String.class);
this.myParameter = myParameter;
}
#Override
public String convertTo(MyObject source, String destination) {
// Using value of myParamter which passed in constructor
// business logic
return destination;
}
#Override
public MyObject convertFrom(String source, MyObject destination) {
// business logic
return null;
}
}
Also if it possible, so how can i send this parameter to constructor so it will be dynamic not static?
Note: i am using dozer inside spring-boot project
In your configuration class you need to add this section:
#Bean
public DozerBeanMapper mapper() throws IOException {
List<String> mappingFiles = new ArrayList<String>();
List<CustomConverter> customConverters = new ArrayList<CustomConverter>();
customConverters.add(new MyCustomDozerConverter(""));
DozerBeanMapper mapper = new DozerBeanMapper();
mapper.setMappingFiles(mappingFiles);
mapper.setCustomConverters(customConverters);
return mapper;
}
In this way dozer will use instance of MyCustomConverter you set in mapper.setCustomConverters(customConverters) method.
Related
I'm working with a 3rd party JSON API, it returns data like this:
{details: {...}, ...}
I use Java Jackson to deserialize this JSON string into a POJO object, the field declaration is :
#JsonProperty("details")
public Details getDetails(){...}
and Details is another class.
Everything is fine until I found that API may return data like this:
{details: false, ...}
If details is empty, it returns false!!! And jackson gave me this exception:
com.fasterxml.jackson.databind.JsonMappingException: Can not instantiate value of type [simple type, class Details] from Boolean value; no single-boolean/Boolean-arg constructor/factory method (through reference chain: ...["details"])
So, how to handle this kind of JSON string? I only need this field to set to null if empty.
The error message from Jackson hints that the library has bulit in support for static factory methods. This is (perhaps) a simpler solution than a custom deserializer:
I created this example POJO, with a static factory method, annotated so that Jackson uses it:
public class Details {
public String name; // example property
#JsonCreator
public static Details factory(Map<String,Object> props) {
if (props.get("details") instanceof Boolean) return null;
Details details = new Details();
Map<String,Object> detailsProps = (Map<String,Object>)props.get("details");
details.name = (String)detailsProps.get("name");
return details;
}
}
test method:
public static void main(String[] args)
{
String fullDetailsJson = "{\"details\": {\"name\":\"My Details\"}} ";
String emptyDetailsJson = "{\"details\": false} ";
ObjectMapper mapper = new ObjectMapper();
try {
Details details = mapper.readValue(fullDetailsJson, Details.class);
System.out.println(details.name);
details = mapper.readValue(emptyDetailsJson, Details.class);
System.out.println(details);
} catch (Exception e) {
e.printStackTrace();
}
}
result is as expected:
My Details
null
Make a custom JsonDeserializer to handle deserializing your Details object in which you either return null if you get false or pass the object to the default deserializer if it's an actual object. Pseudocode:
public class CustomDeserializer extends JsonDeserializer<Details>{
#Override
public Details deserialize(JsonParser jsonParser, DeserializationContext ctx){
//if object use default deserializer else return null
}
}
You'll also have to write an ObjectMapperProvider to register your deserializer like so:
#Provider
public class ObjectMapperProvider implements ContextResolver<ObjectMapper>{
private ObjectMapper mapper;
public ObjectMapperProvider(){
mapper = new ObjectMapper();
SimpleModule sm = new SimpleModule();
sm.addDeserializer(Details.class, new CustomDeserializer());
mapper.registerModule(sm);
}
public ObjectMapper getContext(Class<?> arg0){
return mapper;
}
}
We're developing a RESTful API using Java EE 7 (RESTEasy / Hibernate / Jackson).
We want the API to serialize all child entities using their IDs, by default. We're doing this mostly to maintain consistency with our deserialization strategy, where we insist on receiving an ID.
However, we also want our users to be able to choose to get an expanded view of any of our child entities, either through a custom endpoint or a query parameter (undecided). For example:
// http://localhost:8080/rest/operator/1
// =====================================
{
"operatorId": 1,
"organization": 34,
"endUser": 23
}
// http://localhost:8080/rest/operator/1?expand=organization
// =====================================
{
"operatorId": 1,
"organization": {
"organizationId": 34,
"organizationName": "name"
},
"endUser": 23
}
// http://localhost:8080/rest/operator/1?expand=enduser
// =====================================
{
"operatorId": 1,
"organization": 34,
"endUser": {
"endUserId": 23,
"endUserName": "other name"
}
}
// http://localhost:8080/rest/operator/1?expand=organization,enduser
// =====================================
{
"operatorId": 1,
"organization": {
"organizationId": 34,
"organizationName": "name"
},
"endUser": {
"endUserId": 23,
"endUserName": "other name"
}
}
Is there a way to dynamically change the behavior of Jackson to determine whether a specified AbstractEntity field is serialized in full form or as its ID? How might it be done?
Additional Info
We know of a few ways to serialize our child entities using their IDs, including:
public class Operator extends AbstractEntity {
...
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="organizationId")
#JsonIdentityReference(alwaysAsId=true)
public getOrganization() { ... }
...
}
and
public class Operator extends AbstractEntity {
...
#JsonSerialize(using=AbstractEntityIdSerializer.class)
public getOrganization() { ... }
...
}
where AbstractEntityIdSerializer serializes the entity using its ID.
The problem is that we don't know of a way for the user to override that default behavior and revert to standard Jackson object serialization. Ideally they'd also be able to choose which child properties to serialize in full form.
It would be awesome to dynamically toggle the alwaysAsId argument of #JsonIdentityReference for any property at runtime, if that's possible, or make the equivalent change to ObjectMapper/ObjectWriter.
Update: Working(?) Solution
We haven't had a chance to fully test this yet, but I've been working on a solution that leverages overriding Jackson's AnnotationIntrospector class. It seems to be working as intended.
public class CustomAnnotationIntrospector extends JacksonAnnotationIntrospector {
private final Set<String> expandFieldNames_;
public CustomAnnotationIntrospector(Set<String> expandFieldNames) {
expandFieldNames_ = expandFieldNames;
}
#Override
public ObjectIdInfo findObjectReferenceInfo(Annotated ann, ObjectIdInfo objectIdInfo) {
JsonIdentityReference ref = _findAnnotation(ann, JsonIdentityReference.class);
if (ref != null) {
for (String expandFieldName : expandFieldNames_) {
String expandFieldGetterName = "get" + expandFieldName;
String propertyName = ann.getName();
boolean fieldNameMatches = expandFieldName.equalsIgnoreCase(propertyName);
boolean fieldGetterNameMatches = expandFieldGetterName.equalsIgnoreCase(propertyName);
if (fieldNameMatches || fieldGetterNameMatches) {
return objectIdInfo.withAlwaysAsId(false);
}
}
objectIdInfo = objectIdInfo.withAlwaysAsId(ref.alwaysAsId());
}
return objectIdInfo;
}
}
At serialization time, we copy our ObjectMapper (so the AnnotationIntrospector runs again) and apply CustomAnnotationIntrospector as follows:
#Context
private HttpRequest httpRequest_;
#Override
writeTo(...) {
// Get our application's ObjectMapper.
ContextResolver<ObjectMapper> objectMapperResolver = provider_.getContextResolver(ObjectMapper.class,
MediaType.WILDCARD_TYPE);
ObjectMapper objectMapper = objectMapperResolver.getContext(Object.class);
// Get Set of fields to be expanded (pre-parsed).
Set<String> fieldNames = (Set<String>)httpRequest_.getAttribute("ExpandFields");
if (!fieldNames.isEmpty()) {
// Pass expand fields to AnnotationIntrospector.
AnnotationIntrospector expansionAnnotationIntrospector = new CustomAnnotationIntrospector(fieldNames);
// Replace ObjectMapper with copy of ObjectMapper and apply custom AnnotationIntrospector.
objectMapper = objectMapper.copy();
objectMapper.setAnnotationIntrospector(expansionAnnotationIntrospector);
}
ObjectWriter objectWriter = objectMapper.writer();
objectWriter.writeValue(...);
}
Any glaring flaws in this approach? It seems relatively straightforward and is fully dynamic.
The answer is Jackson's mixin feature:
You create a simple Java class that has the exact same method signature as the anotated method of the entity. You annotate that method with the modified value. the body of the method is insignificant (it would not be called):
public class OperatorExpanded {
...
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="organizationId")
#JsonIdentityReference(alwaysAsId=false)
public Organization getOrganization() { return null; }
...
}
you tie the mixin to the entity-to-be-serialized using Jackson's module system: this can be decided at run time
ObjectMapper mapper = new ObjectMapper();
if ("organization".equals(request.getParameter("exapnd")) {
SimpleModule simpleModule = new SimpleModule();
simpleModule.setMixInAnnotation(Operator.class, OperatorExpanded.class);
mapper.registerModule(simpleModule);
}
now, the mapper will take the annotations from the mixin, but invoke the method of the entity.
If you are looking for a generalized solution that needs to be extended to all of your resources you may try following approach. I tried below solution using Jersey and Jackson. It should also work with RestEasy.
Basically, you need to write a custom jackson provider which set a special serializer for an expand field. Also, you need to pass the expand fields to the serializer so that you can decide how to do the serialization for expand fields.
#Singleton
public class ExpandFieldJacksonProvider extends JacksonJaxbJsonProvider {
#Inject
private Provider<ContainerRequestContext> provider;
#Override
protected JsonEndpointConfig _configForWriting(final ObjectMapper mapper, final Annotation[] annotations, final Class<?> defaultView) {
final AnnotationIntrospector customIntrospector = mapper.getSerializationConfig().getAnnotationIntrospector();
// Set the custom (user) introspector to be the primary one.
final ObjectMapper filteringMapper = mapper.setAnnotationIntrospector(AnnotationIntrospector.pair(customIntrospector, new JacksonAnnotationIntrospector() {
#Override
public Object findSerializer(Annotated a) {
// All expand fields should be annotated with '#ExpandField'.
ExpandField expField = a.getAnnotation(ExpandField.class);
if (expField != null) {
// Use a custom serializer for expand field
return new ExpandSerializer(expField.fieldName(), expField.idProperty());
}
return super.findSerializer(a);
}
}));
return super._configForWriting(filteringMapper, annotations, defaultView);
}
#Override
public void writeTo(final Object value, final Class<?> type, final Type genericType, final Annotation[] annotations, final MediaType mediaType, final MultivaluedMap<String, Object> httpHeaders,
final OutputStream entityStream) throws IOException {
// Set the expand fields to java's ThreadLocal so that it can be accessed in 'ExpandSerializer' class.
ExpandFieldThreadLocal.set(provider.get().getUriInfo().getQueryParameters().get("expand"));
super.writeTo(value, type, genericType, annotations, mediaType, httpHeaders, entityStream);
// Once the serialization is done, clear ThreadLocal
ExpandFieldThreadLocal.remove();
}
ExpandField.java
#Retention(RUNTIME)
public #interface ExpandField {
// name of expand field
String fieldName();
// name of Id property in expand field. For eg: oraganisationId
String idProperty();
}
ExpandFieldThreadLocal.java
public class ExpandFieldThreadLocal {
private static final ThreadLocal<List<String>> _threadLocal = new ThreadLocal<>();
public static List<String> get() {
return _threadLocal.get();
}
public static void set(List<String> expandFields) {
_threadLocal.set(expandFields);
}
public static void remove() {
_threadLocal.remove();
}
}
ExpandFieldSerializer.java
public static class ExpandSerializer extends JsonSerializer<Object> {
private String fieldName;
private String idProperty;
public ExpandSerializer(String fieldName,String idProperty) {
this.fieldName = fieldName;
this.idProperty = idProperty;
}
#Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
// Get expand fields in current request which is set in custom jackson provider.
List<String> expandFields = ExpandFieldThreadLocal.get();
if (expandFields == null || !expandFields.contains(fieldName)) {
try {
// If 'expand' is not present in query param OR if the 'expand' field does not contain this field, write only id.
serializers.defaultSerializeValue(value.getClass().getMethod("get"+StringUtils.capitalize(idProperty)).invoke(value),gen);
} catch (Exception e) {
//Handle Exception here
}
} else {
serializers.defaultSerializeValue(value, gen);
}
}
}
Operator.java
public class Operator extends AbstractEntity {
...
#ExpandField(fieldName = "organization",idProperty="organizationId")
private organization;
...
}
The final step is to register the new ExpandFieldJacksonProvider. In Jersey, we register it through an instance of javax.ws.rs.core.Application as shown below. I hope there is something similar in RestEasy. By default, most of the JAX-RS libraries tend to load default JacksonJaxbJsonProvider through auto-discovery. You have to make sure auto-discovery is disabled for Jackson and new ExpandFieldJacksonProvider is registered.
public class JaxRsApplication extends Application{
#Override
public Set<Class<?>> getClasses() {
Set<Class<?>> clazzes=new HashSet<>();
clazzes.add(ExpandFieldJacksonProvider.class);
return clazzes;
}
}
Is there a way where we can add ObjectMapper for a particular class through annotation.
#JsonRootName("employee")
public class Sample implements Serializable{
private String name;
private String id;
// Getters and setters
}
In the RestController i have RequestMapping and a method like:-
#ResponseBody
public Sample (#RequestBody Sample sample){
//some logic
return sample;
}
My input payload to this will be like
{
"employee":{
"name":"abcd",
"id":"1234"
}
}
My desired output would be
{
"name":"abcd",
"id":"1234"
}
1)Is there a way i can use the same class to fulfill the input and the output.
2) I have added #JsonRootName at the top of the class which requires ObjectMapper's Serialization feature enable to WRAP_ROOT_VALUE like :-
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.WRAP_ROOT_VALUE);
mapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE);
where this can be added to reflect in only this class.
Maybe just leave the default serialization behavior? Then, at deserialization you would still pull out the "employee" wrapper, but at serialization you would write it without the wrapper.
ObjectMapper mapper = new ObjectMapper();
//mapper.enable(SerializationFeature.WRAP_ROOT_VALUE);
mapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE);
With your input, I got the desired serialization output:
{"name":"abcd","id":"1234"}
EDIT
As for where to put this code, I'd recommend a singleton or class with static methods that handle your (de)serialization. You could have two different mappers than perform the "normal" or "wrapped" behavior. Here's an outline of the static method approach:
public class SerializationUtil {
private static ObjectMapper normalObjectMapper;
private static ObjectMapper wrappedObjectMapper;
static {
/* configure different (de)serialization strategies */
normalObjectMapper = new ObjectMapper();
wrappedObjectMapper = new ObjectMapper();
wrappedObjectMapper.enable(SerializationFeature.WRAP_ROOT_VALUE);
wrappedObjectMapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE);
}
public static <T> T normalDeserialize(String json, Class<T> clazz) throws Exception {
return normalObjectMapper.readValue(json, clazz);
}
public static String normalSerialize(Object bean) throws Exception {
return normalObjectMapper.writeValueAsString(bean);
}
public static <T> T deserializeWrappedObject(String json, Class<T> clazz) throws Exception {
return wrappedObjectMapper.readValue(json, clazz);
}
public static String serializeWrappedObject(Object bean) throws Exception {
return wrappedObjectMapper.writeValueAsString(bean);
}
}
The benefit of this method is it allows the caller to decide the serialization behavior. So if there are portions of your code where you need to handle it differently you can call another method. Note that the wrapping/unwrapping are both enabled. So to get your desired behavior, you would call these methods like so:
public static void main(String[] args) {
Bean bean = SerializationUtil.deserializeWrappedObject(jsonInput, Bean.class);
String jsonOutput = SerializationUtil.normalSerialize(bean);
}
If this does not appeal to you, you could alternatively detect the special case and handle it in the same method call:
public static <T> T deserialize(String json, Class<T> clazz) throws Exception {
if (clazz instanceof Bean) {
return wrappedObjectMapper.readValue(json, clazz);
} else {
return normalObjectMapper.readValue(json, clazz);
}
}
I need to add a custom Jackson deserializer for java.lang.String to my Spring 4.1.x MVC application. However all answers (such as this) refer to configuring the ObjectMapper for the complete web application and the changes will apply to all Strings across all #RequestBody in all controllers.
I only want to apply the custom deserialization to #RequestBody arguments used within particular controllers. Note that I don't have the option of using #JsonDeserialize annotations for the specific String fields.
Can you configure custom deserialization for specific controllers only?
To have different deserialization configurations you must have different ObjectMapper instances but out of the box Spring uses MappingJackson2HttpMessageConverter which is designed to use only one instance.
I see at least two options here:
Move away from MessageConverter to an ArgumentResolver
Create a #CustomRequestBody annotation, and an argument resolver:
public class CustomRequestBodyArgumentResolver implements HandlerMethodArgumentResolver {
private final ObjectMapperResolver objectMapperResolver;
public CustomRequestBodyArgumentResolver(ObjectMapperResolver objectMapperResolver) {
this.objectMapperResolver = objectMapperResolver;
}
#Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.getParameterAnnotation(CustomRequestBody.class) != null;
}
#Override
public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
if (this.supportsParameter(methodParameter)) {
ObjectMapper objectMapper = objectMapperResolver.getObjectMapper();
HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
return objectMapper.readValue(request.getInputStream(), methodParameter.getParameterType());
} else {
return WebArgumentResolver.UNRESOLVED;
}
}
}
#CustomRequestBody annotation:
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface CustomRequestBody {
boolean required() default true;
}
ObjectMapperResolver is an interface we will be using to resolve actual ObjectMapper instance to use, I will discuss it below. Of course if you have only one use case where you need custom mapping you can simply initialize your mapper here.
You can add custom argument resolver with this configuration:
#Configuration
public class WebConfiguration extends WebMvcConfigurerAdapter {
#Bean
public CustomRequestBodyArgumentResolver customBodyArgumentResolver(ObjectMapperResolver objectMapperResolver) {
return new CustomRequestBodyArgumentResolver(objectMapperResolver)
}
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(customBodyArgumentResolver(objectMapperResolver()));
}
}
Note: Do not combine #CustomRequestBody with #RequestBody, it will be ignored.
Wrap ObjectMapper in a proxy that hides multiple instances
MappingJackson2HttpMessageConverter is designed to work with only one instance of ObjectMapper. We can make that instance a proxy delegate. This will make working with multiple mappers transparent.
First of all we need an interceptor that will translate all method invocations to an underlying object.
public abstract class ObjectMapperInterceptor implements MethodInterceptor {
#Override
public Object invoke(MethodInvocation invocation) throws Throwable {
return ReflectionUtils.invokeMethod(invocation.getMethod(), getObject(), invocation.getArguments());
}
protected abstract ObjectMapper getObject();
}
Now our ObjectMapper proxy bean will look like this:
#Bean
public ObjectMapper objectMapper(ObjectMapperResolver objectMapperResolver) {
ProxyFactory factory = new ProxyFactory();
factory.setTargetClass(ObjectMapper.class);
factory.addAdvice(new ObjectMapperInterceptor() {
#Override
protected ObjectMapper getObject() {
return objectMapperResolver.getObjectMapper();
}
});
return (ObjectMapper) factory.getProxy();
}
Note: I had class loading issues with this proxy on Wildfly, due to its modular class loading, so I had to extend ObjectMapper (without changing anything) just so I can use class from my module.
It all tied up together using this configuration:
#Configuration
public class WebConfiguration extends WebMvcConfigurerAdapter {
#Bean
public MappingJackson2HttpMessageConverter jackson2HttpMessageConverter() {
return new MappingJackson2HttpMessageConverter(objectMapper(objectMapperResolver()));
}
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(jackson2HttpMessageConverter());
}
}
ObjectMapperResolver implementations
Final piece is the logic that determines which mapper should be used, it will be contained in ObjectMapperResolver interface. It contains only one look up method:
public interface ObjectMapperResolver {
ObjectMapper getObjectMapper();
}
If you do not have a lot of use cases with custom mappers you can simply make a map of preconfigured instances with ReqeustMatchers as keys. Something like this:
public class RequestMatcherObjectMapperResolver implements ObjectMapperResolver {
private final ObjectMapper defaultMapper;
private final Map<RequestMatcher, ObjectMapper> mapping = new HashMap<>();
public RequestMatcherObjectMapperResolver(ObjectMapper defaultMapper, Map<RequestMatcher, ObjectMapper> mapping) {
this.defaultMapper = defaultMapper;
this.mapping.putAll(mapping);
}
public RequestMatcherObjectMapperResolver(ObjectMapper defaultMapper) {
this.defaultMapper = defaultMapper;
}
#Override
public ObjectMapper getObjectMapper() {
ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = sra.getRequest();
for (Map.Entry<RequestMatcher, ObjectMapper> entry : mapping.entrySet()) {
if (entry.getKey().matches(request)) {
return entry.getValue();
}
}
return defaultMapper;
}
}
You can also use a request scoped ObjectMapper and then configure it on a per-request basis. Use this configuration:
#Bean
public ObjectMapperResolver objectMapperResolver() {
return new ObjectMapperResolver() {
#Override
public ObjectMapper getObjectMapper() {
return requestScopedObjectMapper();
}
};
}
#Bean
#Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public ObjectMapper requestScopedObjectMapper() {
return new ObjectMapper();
}
This is best suited for custom response serialization, since you can configure it right in the controller method. For custom deserialization you must also use Filter/HandlerInterceptor/ControllerAdvice to configure active mapper for current request before the controller method is triggered.
You can create interface, similar to ObjectMapperResolver:
public interface ObjectMapperConfigurer {
void configureObjectMapper(ObjectMapper objectMapper);
}
Then make a map of this instances with RequstMatchers as keys and put it in a Filter/HandlerInterceptor/ControllerAdvice similar to RequestMatcherObjectMapperResolver.
P.S. If you want to explore dynamic ObjectMapper configuration a bit further I can suggest my old answer here. It describes how you can make dynamic #JsonFilters at run time. It also contains my older approach with extended MappingJackson2HttpMessageConverter that I suggested in comments.
Probably this would help, but it ain't pretty. It would require AOP. Also I did not validate it.
Create a #CustomAnnotation.
Update your controller:
void someEndpoint(#RequestBody #CustomAnnotation SomeEntity someEntity);
Then implemment the AOP part:
#Around("execution(* *(#CustomAnnotation (*)))")
public void advice(ProceedingJoinPoint proceedingJoinPoint) {
// Here you would add custom ObjectMapper, I don't know another way around it
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
String body = request .getReader().lines().collect(Collectors.joining(System.lineSeparator()));
SomeEntity someEntity = /* deserialize */;
// This could be cleaner, cause the method can accept multiple parameters
proceedingJoinPoint.proceed(new Object[] {someEntity});
}
You can create custom deserializer for your String data.
Custom Deserializer
public class CustomStringDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String str = p.getText();
//return processed String
}
}
Now suppose the String is present inside a POJO use #JsonDeserialize annotation above the variable:
public class SamplePOJO{
#JsonDeserialize(using=CustomStringDeserializer.class)
private String str;
//getter and setter
}
Now when you return it as a response it will be Deserialized in the way you have done it in CustomDeserializer.
Hope it helps.
You could try Message Converters.
They have a context about http input request (for example, docs see here, JSON). How to customize you could see here.
Idea that you could check HttpInputMessage with special URIs, which used in your controllers and convert string as you want.
You could create special annotation for this, scan packages and do it automatically.
Note
Likely, you don't need implementation of ObjectMappers. You can use simple default ObjectMapper to parse String and then convert string as you wish.
In that case you would create RequestBody once.
You can define a POJO for each different type of request parameter that you would like to deserialize. Then, the following code will pull in the values from the JSON into the object that you define, assuming that the names of the fields in your POJO match with the names of the field in the JSON request.
ObjectMapper mapper = new ObjectMapper();
YourPojo requestParams = null;
try {
requestParams = mapper.readValue(JsonBody, YourPOJO.class);
} catch (IOException e) {
throw new IOException(e);
}
I am trying to be able to define the following code:
public class MyObject {
private String name;
... // Other attributes
}
#Path(...)
#Stateless
public class MyRestResource {
#POST
#Consumes(MediaType.APPLICATION_JSON)
public Response create(List<MyObject> myObjects) {
// Do some stuff there
}
}
I know that I need to use:
DeserializationConfig.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true
to setup correctly my object mapper to be able to accept single value as array on my rest resources. I succeed to setup that part.
My problem with this approach is that the following content is not differentiable:
{
"name": "a name",
... // other attributes
}
and
[{
"name": "a name",
... // other attributes
}]
will result into a list (List) of size one. Then, in the method create(List myObjects), I will not be able to do the difference between the List and the Single Object sent to the Rest Resource.
Then, my question is how to do something like that. The idea is to have only one #POST that accepts both Arrays and Single values?
Ideally, I will get rid of the configuration of the ObjectMapper to avoid letting the possibility to set Single Object into the other level of the JSON document. For example, I do not want to allow that:
{
...
"attributes": {
...
}
}
where normally this format should be mandatory:
{
...
"attributes": [{
...
}]
}
Based on that, I tried to put in place an object wrapper of my List to set if I am able to the difference between the list and the object. With something like that:
public class ObjectWrapper<T> {
private List<T> list;
private T object;
public boolean isObject() {
return list == null;
}
}
with the resource that becomes:
#Path(...)
#Stateless
public class MyRestResource {
#POST
#Consumes(MediaType.APPLICATION_JSON)
public Response create(ObjectWrapper myObjects) {
// Do some stuff there
}
}
and trying to put in place the deserialization of my content through the JAX-RS/Jersey/Jackson mechanisms. If I let the solution as it is now, the deserialization fails due to the fact that the JSON format expected is the following:
{
"list": [{
"name": "a name",
... // other attributes
}]
}
Then I tried to write a custom deserializer but I am a bit lost in this task. I have something like that:
public class ObjectWrapperDeserializer<T> extends JsonDeserializer<T> {
#Override
public T deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
... // What to put there to deserialize Array or Object
}
}
I just want to deserialize the root level to set the content deserialized into the object wrapper. I also want to keep the feature configured in a class annotated with #ApplicationPath when the configuraiton of the different #Provider are done.
I hope that all the info will give a sufficient picture of what I want to do and what I already tested.
Waiting for suggestion on how to do a resource that accept Arrays or Objects on the same path.
Thanks a lot in advance.
Ok, finally I succeed to put in place a mechanism that do exactly what I am looking for. But, I am not sure if there are negative consequences such the performance or such things.
First, I defined a class that can accept both List or Single Object:
public class RootWrapper<T> {
private List<T> list;
private T object;
}
Then, I need a custom deserializer that is able to know which kind of T type to deserialize and to handle the collection or the single object.
public class RootWrapperDeserializer extends JsonDeserializer<CollectionWrapper<?>> {
private Class contentType;
public RootWrapperDeserializer(Class contentType) {
this.contentType = contentType;
}
#Override
public RootWrapper deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
// Retrieve the object mapper and read the tree.
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
JsonNode root = mapper.readTree(jp);
RootWrapper wrapper = new RootWrapper();
// Check if the root received is an array.
if (root.isArray()) {
List list = new LinkedList();
// Deserialize each node of the array using the type expected.
Iterator<JsonNode> rootIterator = root.getElements();
while (rootIterator.hasNext()) {
list.add(mapper.readValue(rootIterator.next(), contentType));
}
wrapper.setList(list);
}
// Deserialize the single object.
else {
wrapper.setObject(mapper.readValue(root, contentType));
}
return wrapper;
}
}
As far as I know, I try to only deserialize the root level manually and then let Jackson take the next operations in charge. I only have to know which real type I expect to be present in the Wrapper.
At this stage, I need a way to tell Jersey/Jackson which deserializer to use. One way I found for that is to create a sort of deserializer registry where are stored the type to deserialize with the right deserializer. I extended the Deserializers.Base class for that.
public class CustomDeserializers extends Deserializers.Base {
// Deserializers caching
private Map<Class, RootWrapperDeserializer> deserializers = new HashMap<>();
#Override
public JsonDeserializer<?> findBeanDeserializer(JavaType type,
DeserializationConfig config, DeserializerProvider provider,
BeanDescription beanDesc, BeanProperty property) throws JsonMappingException {
// Check if we have to provide a deserializer
if (type.getRawClass() == RootWrapper.class) {
// Check the deserializer cache
if (deserializers.containsKey(type.getRawClass())) {
return deserializers.get(type.getRawClass());
}
else {
// Create the new deserializer and cache it.
RootWrapperDeserializer deserializer =
new RootWrapperDeserializer(type.containedType(0).getRawClass());
deserializers.put(type.getRawClass(), deserializer);
return deserializer;
}
}
return null;
}
}
Ok, then I have my deserializers registry that create new deserializer only on demand and keep them once created. What I am not sure about that approach is if there is any concurrency issue. I know that Jackson do a lot of caching and do not call every time the findBeanDeserializer once it was called a first time on a specific deserialization context.
Now I have created my different classes, I need to do some plumbing to combine everything together. In a provider where I create the ObjectMapper, I can setup the deserializers registry to the created object mapper like below:
#Provider
#Produces(MediaType.APPLICATION_JSON)
public class JsonObjectMapper implements ContextResolver<ObjectMapper> {
private ObjectMapper jacksonObjectMapper;
public JsonObjectMapper() {
jacksonObjectMapper = new ObjectMapper();
// Do some custom configuration...
// Configure a new deserializer registry
jacksonObjectMapper.setDeserializerProvider(
jacksonObjectMapper.getDeserializerProvider().withAdditionalDeserializers(
new RootArrayObjectDeserializers()
)
);
}
#Override
public ObjectMapper getContext(Class<?> arg0) {
return jacksonObjectMapper;
}
}
Then, I can also define my #ApplicationPath that is my REST application like following:
public abstract class AbstractRestApplication extends Application {
private Set<Class<?>> classes = new HashSet<>();
public AbstractRestApplication() {
classes.add(JacksonFeature.class);
classes.add(JsonObjectMapper.class);
addResources(classes);
}
#Override
public Set<Class<?>> getClasses() {
return classes;
}
#Override
public Set<Object> getSingletons() {
final Set<Object> singletons = new HashSet<>(1);
singletons.add(new JacksonJsonProvider());
return singletons;
}
private void addResources(Set<Class<?>> classes) {
classes.add(SomeRestResource.class);
// ...
}
}
Now, everything is in place and I can write a REST resource method like that:
#POST
#Path("somePath")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public Response create(RootWrapper<SpecificClass> wrapper) {
if (wrapper.isObject()) {
// Do something for one single object
SpecificClass sc = wrapper.getObject();
// ...
return Response.ok(resultSingleObject).build();
}
else {
// Do something for list of objects
for (SpecificClass sc = wrapper.getList()) {
// ...
}
return Response.ok(resultList).build();
}
}
That's all. Do not hesitate to comment the solution. Feedbacks are really welcome especially around the way of deserialization process where I am really not sure that it is safe for performance and concurrency.