jackson map serialization, custom serializer for the key do not invoked - java

I need to have functional which allow me to serialize Map<CustomType1, CustomType2>.
I create custom Serializer inherited from JsonSerializer.
I also create simple module and register it in my mapper;
SimpleModule myModule = new SimpleModule("myModule");
myModule.addKeySerializer(CustomType1.class, new CustomType1Serializer());
myModule.addSerializer(CustomType1.class, new CustomType1Serializer());
mapperInstance.registerModule(myModule);
And when I just serializing an instance of CustomType1 it works perfectly, but when I creating map and trying to serialize it, than jackson skip my serializer and using StdKeySerializer. How to fix that???
Thanks for your attention.

This problem seems related to Jackson's handling of generic objects. One way to get around the issue is by using a super type token to strictly define the map type. Illustrated:
final ObjectMapper mapper = new ObjectMapper();
final SimpleModule module = new SimpleModule("myModule",
Version.unknownVersion());
module.addKeySerializer(CustomType1.class, new CustomType1Serializer());
mapper.registerModule(module);
final MapType type = mapper.getTypeFactory().constructMapType(
Map.class, CustomType1.class, CustomType2.class);
final Map<CustomType1, CustomType2> map = new HashMap<CustomType1, CustomType2>(4);
final ObjectWriter writer = mapper.writerWithType(type);
final String json = writer.writeValueAsString(map);

addSerializer and addKeySerializer are just two types of available serializers that deal only with simple, non-POJO types. To have custom serialization for more complex types such as maps and collections, you need to .setSerializerModifier on your module, with a BeanSerializerModifier that overrides the modifyMapSerializer method and returns your custom serializer

Related

com.fasterxml.jackson.databind.JsonMappingException: Cannot invoke "java.lang.Boolean.booleanValue()" because "this.flag" is null

I'm trying to use Jackson in case to serialize and then deserialize an object. The object contains a field -> protected Serializable data
This is where the problem comes from - this data can come from a lot of components. That is why I'm trying to configure the Jackson ObjectMapper instead of adding annotations and changing the code (because for some of the issues I have with the serialization, like this one, I have to change like hundreds of different classes). Is there any way to tell the ObjectMapper to not use .booleanValue() on null fields.
This is how I've currently configured my ObjectMapper:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(objectMapper.getSerializationConfig().getDefaultVisibilityChecker()
.withFieldVisibility(JsonAutoDetect.Visibility.ANY)
.withGetterVisibility(JsonAutoDetect.Visibility.NONE)
.withSetterVisibility(JsonAutoDetect.Visibility.NONE)
.withCreatorVisibility(JsonAutoDetect.Visibility.NONE));
This usually happens when your object has a property definition of type Boolean but a getter of type boolean. Ensure these types match.

How to deserialize Spring Boot actuator environment

I would like to deserialize the Spring Boot Environment object returned by:
http://hostname:port/actuator/env
I'm using Jackson's ObjectMapper:
private static final ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
...
ClientResponse clientResponse = resource.type(MediaType.APPLICATION_JSON).get(ClientResponse.class);
InputStream is = clientResponse.getEntityInputStream();
org.springframework.core.env.Environment e = mapper.readValue(is, org.springframework.core.env.Environment.class);
The code above fails with the following error, which makes sense:
com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of org.springframework.core.env.Environment, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information
But I've tried all the implementations of the Environment class (AbstractEnvironment, MockEnvironment, StandardEnvironment, StandardServletEnvironment) and they all fail as well.
Which class should I use?
org.springframework.core.env.Environment is an interface. So ObjectMapper can not guess what concrete class to instantiate. You need to tell your ObjectMapper which class to use. So in your line
org.springframework.core.env.Environment e = mapper.readValue(is,org.springframework.core.env.Environment.class); You need to replace org.springframework.core.env.Environment.class with some concrete class. For example org.springframework.core.env.StandardEnvironment (depending on what kind of environment actually being returned). Otherwise de-serialize it to map:
Map<String, Object> map = mapper.readValue(is,HashMap<String, Object>.class);
And then go from there

Jackson: how to deserialize nested custom maps and lists?

I'm trying to deserialize an untyped JSON file into a custom implementation of Map interface.
This custom map implementation can have only java simple types (Date, String, Integer, Long) and nested types (Custom Map implementation for nested map and custom List implementation for nested arrays or lists)
I tried the following:
CustomMap map = mapper.readValue(myJsonFile, CustomMap.class);
I got the expected type except for nested maps I get HashMap type and for nested arrays I get ArrayList type.
I think Jackson fallbacks on HashMap for unknown types and ArrayList for arrays.
Is it possible to fallback recursively to CustomMap instead of HashMap for unknown subtypes and CustomList for arrays?
Thanks in advance
You can achieve this by creating and registering a SimpleModule on your mapper. This will allow to change the type mapping for Map in your case and other types like List. E.g.
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("CustomCollections", Version.unknownVersion());
module.addAbstractTypeMapping(Map.class, CustomMap.class);
module.addAbstractTypeMapping(List.class, CustomList.class);
mapper.registerModule(module);
You may have to call readValue like this:
Map<String, Object> data = mapper.readValue(myJsonFile, new TypeReference<CustomMap<String, Object>>() {});
Mind that this works in Jackson 2.6.0 and later as there was a bug previously.
Also, Jackson defaults to LinkedHashMap for Map not HashMap, to maintain the order of data in the JSON document.
Solution proposed by Manos works, in my case I need to add a custom deserializer for CustomList, due to a missing implementation of a method.
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("CustomCollections", Version.unknownVersion());
module.addAbstractTypeMapping(Map.class, CustomMap.class);
module.addAbstractTypeMapping(List.class, CustomList.class);
module.addDeserializer(CustomList.class, new CustomListDeserializer());
mapper.registerModule(module);

JAXB to JSON using JACKSON

In my application the JAXB output generates like:
this.marshalOut(jaxb_Object, fileOutputStream);
this is method call to the spring Object XML Mapping Marshallers that generate XML files. Now, I also like to generate JSON files after this line. Any one have idea about generating JSON output using JAXB input.
I found this example code online:
ObjectMapper mapper = new ObjectMapper();
AnnotationIntrospector introspector = new JacksonAnnotationIntrospector();
// make deserializer use JAXB annotations (only)
mapper.getDeserializationConfig().setAnnotationIntrospector(introspector);
// make serializer use JAXB annotations (only)
mapper.getSerializationConfig().setAnnotationIntrospector(introspector);
mapper.writeValue( outputStream, jaxb_object);
The setAnnotationIntrospector is deprecated, is there any other way of solving this problem?
The following works (and does not use any deprecated constructors) :
ObjectMapper mapper = new ObjectMapper();
AnnotationIntrospector introspector =
new JaxbAnnotationIntrospector(mapper.getTypeFactory());
mapper.setAnnotationIntrospector(introspector);
Specifically, this line
new JaxbAnnotationIntrospector(mapper.getTypeFactory());
uses a non-deprecated constructor. I've tested this and it successfully processes JAXB Annotations (such as #XmlTransient, in my case).
You can use jackson-module-jaxb-annotations as stated in the doc you can register a JaxbAnnotationModule module:
JaxbAnnotationModule module = new JaxbAnnotationModule();
// configure as necessary
objectMapper.registerModule(module);
Doing so you can now use both JAXB annotation and Jackson native annotation.
If you update Jackson to 2.0 it is not deprecated:
http://fasterxml.github.com/jackson-databind/javadoc/2.0.0/com/fasterxml/jackson/databind/ObjectMapper.html
You can see my configuration here (Spring):
Registrer MappingJackson2HttpMessageConverter in Spring 3.1.2 with JAXB annotations
The correct solution for me was:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setAnnotationIntrospector(new JaxbAnnotationIntrospector());
According to the Jackson javadoc:
setAnnotationIntrospector
#Deprecated
public final void setAnnotationIntrospector(AnnotationIntrospector ai)
Deprecated. Since 1.8, use either withAnnotationIntrospector(AnnotationIntrospector) or Module API instead
Method for replacing existing annotation introspector(s) with specified introspector. Since this method modifies state of configuration object directly, its use is not recommended
Did you check the method withAnnotationIntrospector(AnnotationIntrospector ai) to see wheter or not it's useful in your case?

Converting java.sql.Timestamp to StringNode when passing map to Jackon's valueToTree method

I have a map Map<String, Object> and some values are of type java.sql.Timestamp. I want to create a JSON node object using Jackson that would convert java.sql.Timestamp to StringNode node using method valueToTree. Using default ObjectMapper, java.sql.Timestamp is converted LongNode.
Extend JsonSerializer class, for a specific type and include that in the mapper via Module
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(TimeStamp.class, new TimeStampSerializer());
mapper.registerModule(module);
APIs might differ based on the version being used.

Categories