Jackson: how to deserialize nested custom maps and lists? - java

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

Related

Jackson reading all numbers as BigDecimal

We are using Jackson to read json from the filesystem and parse it to the POJO.
POJO
String name;
Map<String,Object> map;
getters/setters
Reading
ObjectMapper mapper = new ObjectMapper();
mapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);
Pojo obj = mapper.readValue(jsonFile, Pojo.class);
Problem
When we have numbers in json (map part) they gets converted to Integer Or Double.And we want all our numbers (decimal and whole) as Type BigDecimal So I tried using the
mapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);
But this only works for the decimal numbers. There is no feature available to covert whole numbers to BigDecimal.
Question
Do we have any inbuilt feature to enable ObjectMapper to read all the number to BigDecimal?
If I need to write custom serializer, Do I need to write it for the whole class or it can be written for Map field ?
You can register a Module with your ObjectMapper that includes a custom JsonDeserializer. You don't need to make your own because Jackson Databind provides a BigDecimalDeserializer but you need to do one more thing to make it work.
Because BigDecimalDeserializer is annotated with #JacksonStdImpl, Jackson won't allow you to use this directly because the initialization code (I currently disagree with this) specifically checks for this annotation and disallows it. Because this deserializer is not a final, you can get around this without copy-pasting by creating an anonymous subclass.
In the end, it will look something like this
Module module = new SimpleModule();
module.addDeserializer(Number.class, new NumberDeserializers.BigDecimalDeserializer() {});
new ObjectMapper().registerModule(module).readValue("{}", Map.class);
Try putting :BigDecimal:[dps] is in your json. For example :
{
"MY_BIG_D:BigDecimal:0" : 3
}
where dps = decimal places.

Using Struts 2 builtin JSON utility class

In an Struts 2 project, we need to serialize and deserialize objects, as our requirement is very simple, we decide to use Struts 2 JSONUtil instead of gson.
import org.apache.struts2.json;
String json = JSONUtil.serialize(myAccountVO);
// return: {"accountNumber":"0105069413007","amount":"1500","balance":"215000"}
For deserialization, we face the class cast exception
AccountVO vo =(AccountVO) JSONUtil.deserialize(json);
//Exception
I find that the deserialization returns a map with key value of object properties. So I must do as:
HashMap<String,String> map = (HashMap) JSONUtil.deserialize(string)
accountVo.setAccountNumber(map.get("accountNumber"));
....
Well can I do it better or I am expecting too much from this utility.
After you have deserialized JSON, you can use JSONPopulator to populate bean properties from a map. E.g.
JSONPopulator populator = new JSONPopulator();
AccountVO vo = new AccountVO();
populator.populateObject(vo, map);

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

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

Json, Jackson and serialization

I've started to using Json with Jackson library and i found little problem.
I'm creating Json object:
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> userInMap = new HashMap<String, Object>();
then i'm adding fields:
userInMap.put("user", "active");
userInMap.put("uuid", uuid);
And after all when im trying to output this object i have Json object but without ", i mean i supposed to have:
{"user":"active", "uuid":"lasdnfa"}
but i have:
{user:active, uuid:lasdnfa}
and another thing - i want to add this object to memcache, but before i do this, i have to serialize this object. How i can serialize Json object?
Thanks
If you are using toString() on your object, you might need your mapper to output the value this way :
System.out.println(mapper.writeValueAsString(userInMap)));

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