Object Mapper mapping for a particular class during http calls - java

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);
}
}

Related

How could I create a custom dozer initializer

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.

How java jackson deserializer handle both Boolean and Object on same field

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;
}
}

Can you configure Spring controller specific Jackson deserialization?

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);
}

JSON to Java object deserialization with escaped properties

I need to convert the following JSON to Java object. The property providerResponse in the JSON contains map of properties but they are escaped and wrapped in doubleQuotes. As a result, it does not deserialize the property providerResponse into a Java object (it comes as String). I use objectMapper.readValue(msgStr, classType) to deserialize the JSON. The message is generated by AWS for SNS delivery status notifications and I don't have control to change the JSON message. Is it possible to configure ObjectMapper to unescape the property and deserialize into a Java object instead of String?
{
"delivery":{
"providerResponse":"{\"sqsRequestId\":\"308ee0c6-7d51-57b0-a472-af8e6c41be0b\",\"sqsMessageId\":\"88dd59eb-c34d-4e4d-bb27-7e0d226daa2a\"}"
}
}
#JsonProperty("providerResponse")
private String providerResponse;
There doesn't seem to be a way to configure ObjectMapper to handle this behavior by default. The solution is to create a custom JsonDeserializer:
public class Wrapper {
public Delivery delivery;
}
public class Delivery {
#JsonDeserialize(using = ProviderResponseDeserializer.class)
public ProviderResponse providerResponse;
}
public class ProviderResponse {
public String sqsRequestId;
public String sqsMessageId;
}
public class ProviderResponseDeserializer extends JsonDeserializer<ProviderResponse> {
private static final ObjectMapper mapper = new ObjectMapper();
#Override
public ProviderResponse deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
return mapper.readValue(jsonParser.getText(), ProviderResponse.class);
}
}
Then you can deserialize the JSON by using your ObjectMapper:
ObjectMapper mapper = new ObjectMapper();
Wrapper wrapper = mapper.readValue(JSON, Wrapper.class);
I faced this similar issue. This gets resolved if we define a constructor in ProviderResponse which takes a single string argument (which is actually json) and then map the json in the constructor to the instance of ProviderResponse and use this temp instance to initialise the properties.
public class Wrapper {
public Delivery delivery;
}
public class Delivery {
public ProviderResponse providerResponse;
}
public class ProviderResponse {
public String sqsRequestId;
public String sqsMessageId;
private static ObjectMapper objMapper = new ObjectMapper();
public ProviderResponse(String json) {
ProviderResponse temp = objMapper.readValue(json, ProviderResponse.class);
this.sqsMessageId = temp.sqsMessageId;
this.sqsRequestId = temp.sqsRequestId;
}
}
The key is to keep the ObjectMapper instance and the its usage somewhere in your utility class and use it from there.

How to keep track of classes that Jackson might deserialize to?

In order to impose certain checks on classes that Jackson might deserialize to, I'd like to be able to easily find any such classes. However, in standard usage, Jackson can deserialize into a completely normal looking class that has no Jackson annotations.
A colleague mentioned having previously seen some way to setup Jackson to only successfully deserialize classes that are explicitly annotated as being able to do so. This would present an easy solution, as I could then just find classes with such an annotation. However, looking through all the Jackson docs, I can't find this functionality. Does anyone know where it's to be found/is this deprecated?
Consider an option where you mark your "json" classes with a custom annotation, then set a special annotation introspector which would fail to serialize all the other classes from your application.
Note that you will need to be able differentiate between the standard classes such as primitives, string, collection, etc., which don't have the custom annotation, and the classes from your application which shall not be processed.
Here is an example:
package stackoverflow;
public class JacksonTracking {
#Retention(RetentionPolicy.RUNTIME)
public static #interface Json {
}
#Json
public static class A {
public final String field1;
public A(String field1) {
this.field1 = field1;
}
}
public static class B {
public final String field2;
public B(String field2) {
this.field2 = field2;
}
}
public static class UnsupportedSerializer extends JsonSerializer.None {
private final Class<?> type;
public UnsupportedSerializer(Class<?> type) {
this.type = type;
}
#Override
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
throw new UnsupportedOperationException("Unsupported type: " + type);
}
}
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
mapper.setAnnotationIntrospector(new JacksonAnnotationIntrospector() {
#Override
public Object findSerializer(Annotated a) {
if (a instanceof AnnotatedClass
&& a.getRawType().getPackage().getName().startsWith("stackoverflow")
&& !a.hasAnnotation(Json.class)) {
return new UnsupportedSerializer(a.getRawType());
}
return super.findSerializer(a);
}
});
System.out.println(mapper.writeValueAsString(new A("value1")));
System.out.println(mapper.writeValueAsString(new B("value2")));
}
}
Output:
{"field1":"value1"}
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Unsupported type: class stackoverflow.JacksonTracking$B
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:125)
at com.fasterxml.jackson.databind.ObjectMapper._configAndWriteValue(ObjectMapper.java:2866)
at com.fasterxml.jackson.databind.ObjectMapper.writeValueAsString(ObjectMapper.java:2323)
at stackoverflow.JacksonTracking.main(JacksonTracking.java:71)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
Caused by: java.lang.UnsupportedOperationException: Unsupported type: class stackoverflow.JacksonTracking$B
at stackoverflow.JacksonTracking$UnsupportedSerializer.serialize(JacksonTracking.java:52)
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:114)
... 8 more
On the ObjectMapper that you use for deserialization you can configure auto detection of fields, methods, and creators. If you want to enforce this across your entire application I would recommend extending ObjectMapper and using the custom implementation everywhere. Then any additional config you want to impose can live there as well. Something like this:
public class CustomObjectMapper extends ObjectMapper {
public CustomObjectMapper() {
setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
}
}
You could of course just call setVisibility anywhere you declare an ObjectMapper instead:
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);

Categories

Resources