jackson deserialize string to a parameterized map - java

I have a json string in the following structure:
{
"1": { ... },
"2": { ... },
"3": { ... }
}
where 1, 2 and 3 are identifiers.
I want to deserialize this string and cast it to the following type - Map<Integer, List<MyCustomPojo>>
The thing is that each value { ... } has its own structure, but I need to parse them all and cast to a common structure - MyCustomPojo.
I can do it by implementing a custom deserializer:
public class CustomMapDeserializer extends JsonDeserializer<Map<Integer, List<MyCustomPojo>>> {
#Override
public Map<Integer, List<MyCustomPojo>> deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException {
JsonNode root = jsonParser.getCodec().readTree(jsonParser);
// parse JsonNode and return Map<Integer, List<MyCustomPojo>>
}
Now I need to add this deserializer to a module and then register it with an object mapper.
SimpleModule module = new SimpleModule();
module.addDeserializer(Map.class, new CustomMapDeserializer()); // can’t specify type for map
objectMapper.registerModule(module);
The problem is that now it will be applied to all Map classes in my application. I want it to apply only to parameterized map - Map<Integer, List<MyCustomPojo>>.
Is it possible to do it?

Simplest solution should be to create a new class which inherit from Map and use it when you add deserializer

The way you're thinking of isn't going to work, and might need to rewrite some of the code to apply ONLY to your parameterized map. I don't want to give any false info, sorry I can't provide a solution, but I hope this comment will help others to provide a solution for other abled coders.

Related

How deserialize json object array as array of json strings?

Consider json input:
{
companies: [
{
"id": 1,
"name": "name1"
},
{
"id": 1,
"name": "name1"
}
],
nextPage: 2
}
How deserialize this into class:
public class MyClass {
List<String> companies;
Integer nextPage;
}
Where List<String> companies; consists of strings:
{"id": 1,"name": "name1"}
{"id": 1,"name": "name1"}
#JsonRawValue doesn't work for List<String> companies;
Is there a way to configure Jackson serialization to keep companies array with raw json string with annotations only? (E.g. without writing custom deserializator)
There is no annotation-only solution for your problem. Somehow you have to convert JSON Object to java.lang.String and you need to specify that conversion.
You can:
Write custom deserializer which is probably most obvious solution but forbidden in question.
Register custom com.fasterxml.jackson.databind.deser.DeserializationProblemHandler and handle com.fasterxml.jackson.databind.exc.MismatchedInputException situation in more sophisticated way.
Implement com.fasterxml.jackson.databind.util.Converter interface and convert JsonNode to String. It is semi-annotational way to solve a problem but we do not implement the worst part - deserialisation.
Let's go to point 2. right away.
2. DeserializationProblemHandler
Solution is pretty simple:
ObjectMapper mapper = new ObjectMapper();
mapper.addHandler(new DeserializationProblemHandler() {
#Override
public Object handleUnexpectedToken(DeserializationContext ctxt, JavaType targetType, JsonToken t, JsonParser p, String failureMsg) throws IOException {
if (targetType.getRawClass() == String.class) {
// read as tree and convert to String
return p.readValueAsTree().toString();
}
return super.handleUnexpectedToken(ctxt, targetType, t, p, failureMsg);
}
});
Read a whole piece of JSON as TreeNode and convert it to String using toString method. Helpfully, toString generates valid JSON. Downside, this solution has a global scope for given ObjectMapper instance.
3. Custom Converter
This solution requires to implement com.fasterxml.jackson.databind.util.Converter interface which converts com.fasterxml.jackson.databind.JsonNode to String:
class JsonNode2StringConverter implements Converter<JsonNode, String> {
#Override
public String convert(JsonNode value) {
return value.toString();
}
#Override
public JavaType getInputType(TypeFactory typeFactory) {
return typeFactory.constructType(new TypeReference<JsonNode>() {
});
}
#Override
public JavaType getOutputType(TypeFactory typeFactory) {
return typeFactory.constructType(new TypeReference<String>() {
});
}
}
and now, you can use annotation like below:
#JsonDeserialize(contentConverter = JsonNode2StringConverter.class)
private List<String> companies;
Solutions 2. and 3. solve this problem almost in the same way - read node and convert it back to JSON, but uses different approaches.
If, you want to avoid deserialising and serialising process you can take a look on solution provided in this article: Deserializing JSON property as String with Jackson and take a look at:
How to serialize JSON with array field to object with String field?
How to get a part of JSON as a plain text using Jackson
How to extract part of the original text from JSON with Jackson?

Get the detected generic type inside Jackson's JsonDeserializer

For external reasons, all java Maps in my system can only be received as lists of key-value pairs from the clients, e.g. a Map<String, Book> will actually be received as Json-serialized List<MapEntry<String, Book>>. This means I need to customize my Json deserialization process to expect this representation of maps.
The problem is that JsonDeserializer makes me implement
deserialize(JsonParser p, DeserializationContext ctxt)
method which has no access to the detected generic type it's supposed to deserialize (Map<String, Book> in the example above). Without that info, I can't in turn deserialize List<MapEntry<String, Book>> without loosing type safety.
I was looking at Converter but it gives even less context.
E.g.
public Map<K,V> convert(List<MapToListTypeAdapter.MapEntry<K,V>> list) {
Map<K,V> x = new HashMap<>();
list.forEach(entry -> x.put(entry.getKey(), entry.getValue()));
return x;
}
But this will potentially create dangerous maps that will throw a ClassCastException on retrieval, as there's no way to check the type is actually sensible.
Is there a way to get around this?
As an example of what I'd expect, Gson's JsonDeserializer looks like this:
T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
I.e. it gives access to the expected type in a sane way.
Got an answer on the Jackson Google group directly from the author.
The key thing to understand is that JsonDeserializers are initiated/contextualized once, and they receive the full type and other information at that moment only. To get a hold of this info, the deserializer needs to implement ContextualDeserializer. Its createContextual method is called to initialize a deserializer instance, and has access to the BeanProperty which also gives the full JavaType.
So it could look something like this in the end:
public class MapDeserializer extends JsonDeserializer implements ContextualDeserializer {
private JavaType type;
public MapDeserializer() {
}
public MapDeserializer(JavaType type) {
this.type = type;
}
#Override
public JsonDeserializer<?> createContextual(DeserializationContext deserializationContext, BeanProperty beanProperty) throws JsonMappingException {
//beanProperty is null when the type to deserialize is the top-level type or a generic type, not a type of a bean property
JavaType type = deserializationContext.getContextualType() != null
? deserializationContext.getContextualType()
: beanProperty.getMember().getType();
return new MapDeserializer(type);
}
#Override
public Map deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
//use this.type as needed
}
...
}
Registered and used as normal:
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Map.class, new MapDeserializer());
mapper.registerModule(module);

Custom deserialization of List using Jackson

I am trying to write a custom deserializer in order to trim down a big set of data I receive from somewhere else. I return a List of custom objects from the deserializer.
My question is, how do I do that, if this is my custom deserializer :
public class MyCustomDeserializer extends JsonDeserializer<List<CustomClass>> { ... }
I certainly can't do this :
final SimpleModule module = new SimpleModule();
module.addDeserializer(List<CustomClass>.class, new MyCustomDeserializer());
Will something like this work ?
final List<CustomClass> response = Arrays.asList(objectMapper.readValue(stringBean, CustomClass[].class));
If this indeed works, I find it a bit confusing and "dangerous" ? Isn't the deserialization done inside the asList method invocation ? So it basically maps a List to an array[] ?
I learned about TypeReference so I can probably use that like so :
objectMapper.readValue(stringBean, new TypeReference<List<CustomClass>>(){});
but I heard it is slower.
I also don't want to create a container for the list, and return that in the deserialization because that means it will be wrapped in another json object, and I simply want my endpoint to produce something like :
[{object1}, {object2}]
// instead of
{"Output" : [{object1}, {object2}]}
EDIT:
It seems that I have misinterpreted how jackson is using my deserializer in both cases :
final List<CustomClass> response = Arrays.asList(objectMapper.readValue(stringBean, CustomClass[].class));
// or
objectMapper.readValue(stringBean, new TypeReference<List<CustomClass>>(){});
It looks like the deserializer is called twice, once for each object in the array. I thought that the entire array would be considered as a whole. To clear the confusion, here is what I mean:
The json I receive and try to deserialize looks like so :
[
{
"Data" : {
"id" : "someId",
"otherThing" : "someOtherThing"
},
"Message" : "OK"
},
{
"Data" : null,
"Message" : "Object not found for id blabla"
}
]
and so I though this is what I would have inside my deserializer, but as I said before it seems that i actually get each "entry" from that array and call it multiple times.
First of all, If you registered your custom deserializer using annotation on the bean CustomClass then the deserializer should handle one instance of CustomClass and not a collection and thus should be defined:
public class MyCustomDeserializer extends JsonDeserializer<CustomClass> {
#Override
public CustomClass deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException
{
...
}
}
and now you can use Jackson's type factory to pass the mapper the required type information
JavaType customClassCollection = objectMapper.getTypeFactory().constructCollectionType(List.class, CustomClass.class);
List<CustomClass> beanList = (List<CustomClass>)objectMapper.readValue(stringBean, customClassCollection);
I worked it out by adding a custom deserializer to an attribute in my model class and using JsonDeserialize annotation's contentUsing() method, like so:
#JsonDeserialize(contentUsing = MyCustomDeserializer.class)
private List<CustomClass> customClassObjList;
where MyCustomDeserializer class is a custom Jackson JSON deserializer defined as:
public class MyCustomDeserializer extends JsonDeserializer<CustomClass> {
#Override
public CustomClass deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
...
}
}
These two lines will just do enough.
ArrayNode arrayNode = (ArrayNode) objectMapper.readTree(stringBean);
List<CustomClass> response = objectMapper.convertValue(arrayNode, List.class);
Thank me later!

Using Jackson to deserialize into a Map

I have a JSON object with two attributes: "key" which is a string, and "value" which can be deserialized into a Java bean.
{ "key": "foo", "value": "bar" }
The question is, given a list of such objects, can I deserialize it into a Map?
[{"key": "foo1", "value": "bar1"}, {"key": "foo2", "value": "bar2"}] -> Map<String, String>
Currently using Jackson-databind 2.1
You can easily convert above JSON to List<Map<String, String>>:
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.CollectionType;
public class JacksonProgram {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
CollectionType mapCollectionType = mapper.getTypeFactory().constructCollectionType(List.class, Map.class);
List<Map<String, String>> result = mapper.readValue(json, mapCollectionType);
System.out.println(result);
}
}
Above program prints:
[{key=foo1, value=bar1}, {key=foo2, value=bar2}]
Since your structure does not match, you have two basic options:
Use two-phase handling: use Jackson to bind into intermediate representation that does match (which could be JsonNode or List<Map<String,Object>>), and then do conversion to desired type with simple code
Write custom serializer and/or deserializer
Jackson does not support extensive structural transformations (there are some simple ones, like #JsonUnwrapped), so this kind of functionality is unlikely to be added to databind module. Although it could be added as an extension module, if these "smart Map" types of structures are commonly used (they seem to be, unfortunately).
Had the same problem and was surprised Jackson wasn't able to natively handle it. Solution I went with was to create a custom setter on the object I was trying to marshal into :
public class somePojo {
private Map<String, String> mapStuff;
...
public void SetMapStuff(List<Map<String, String> fromJackson){
mapStuff= new HashMap<>();
for (Map<String, String> pair : fromJackson) {
put(pair.get("key"), pair.get("value"));
}
}
}
Jackson is smart enough to find that setter to and can happily pass it the List.

Jackson deserialize json property into object

I need to deserialize the following json:
{
//...
"foo_id":1
//...
}
Into an object of class Foo with its id property set to the foo_id json property.
I need to do this within a custom deserializer.
What is the most easy way to accomplish this?
I was thinking to somehow "transform" the json to
{
//...
"foo_id":{
"id":1
}
//...
}
and then delegate this back to Jackson.
In this case, the object is of type Foo, but there are others which might not be of this class. Also, in this case, that json is a number, but I would like to support if it was a string as well.
So, I need a kind of generic way to do this, that's why I think delegating back to Jackson might be a good idea.
No annotations allowed. Suppose you're already writing the Deserializer for this property.
Take a look at this. Here is a code that I think might help you to get some ideas.
public class MyDeserializer extends JsonDeserializer< Message >
{
#Override
public Message deserialize( JsonParser jp, DeserializationContext arg1 ) throws IOException,
JsonProcessingException
{
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
ObjectNode root = (ObjectNode) mapper.readTree(jp);
Class<? extends Message> subClass = null;
Iterator<Entry<String, JsonNode>> elementsIterator = root.getFields();
while (elementsIterator.hasNext())
{
Entry<String, JsonNode> element = elementsIterator.next();
String name = element.getKey();
if ("foo_id".equals(name))
{
if(element.getValue().isInt())
subClass = FooInteger.Class;
break;
}
}
if (subClass == null) return null;
return mapper.readValue(root, subClass);
}
}
Have you considered use of mix-in annotations? With Jackson 2.2, you could also use converters to do two-step processing (#JsonDeserialize(converter=MyConverter.class).

Categories

Resources