I am using Jackson to read a user-provided JSON file, mapping it on an object. In case of error in the JSON structure, I get a JsonMappingException that is not readable by the user. For example:
my.package.shadow.jackson.databind.JsonMappingException: Class java.util.ArrayList not subtype of [map type; class java.util.Map, [simple type, class java.lang.String] -> [collection type; class java.util.List, contains [simple type, class java.lang.String]]] (through reference chain: my.package.configuration.Configuration["conf"]->java.util.HashMap["foo"])
at my.package.shadow.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:391)
...
Caused by: java.lang.IllegalArgumentException: Class java.util.ArrayList not subtype of [map type; class java.util.Map, [simple type, class java.lang.String] -> [collection type; class java.util.List, contains [simple type, class java.lang.String]]]
at my.package.shadow.jackson.databind.type.TypeFactory.constructSpecializedType(TypeFactory.java:357)
...
Is there a way to get a more readable error message, or the line and column in the JSON source that corresponds to the exception?
To clarify, the user writes this
{
"conf": {
"foo": ["bar"]
}
}
but I expect
{
"conf": {
"foo": {
"bar" : ["a", "b", "c"]
}
}
}
A snippet of the code:
public class ReadConfiguration {
public static Configuration readConfiguration(File file) {
ObjectMapper mapper = new ObjectMapper(new JsonFactory());
Configuration conf = mapper.readValue(file, Configuration.class);
return conf
}
}
Related
I'm using JSON Jackson on SpringBoot context.
I have a problem with Jackson deserialization with Generic type constraint.
This code work fine :
public abstract class AbstractSyncableWriteAndReadResource<T, K> extends AbstractSyncableReadResource<T, K> {
...
#Timed
#RequestMapping(method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_VALUE)
#Transactional
public ResponseEntity<List<SynQueuDTO<T>>> syncPushPut(#RequestBody List<SynQueuDTO<T>> entities) {
...
}
}
But this code don't work :
This code work fine :
public abstract class AbstractSyncableWriteAndReadResource<T extends EntitySyncableWrite, K> extends AbstractSyncableReadResource<T, K> {
...
#Timed
#RequestMapping(method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_VALUE)
#Transactional
public ResponseEntity<List<SynQueuDTO<T>>> syncPushPut(#RequestBody List<SynQueuDTO<T>> entities) {
...
}
}
There is only one difference : add Java interface on Generic for constraint Class's type.
Exception :
Type definition error: [simple type, class xxx.xxx.xxx.EntitySyncableWrite]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `xxx.xxx.xxx.EntitySyncableWrite` (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information\n at [Source: (PushbackInputStream); line: 5, column: 26]
I try a lot of configurations to force Jackson take Class instead of Interface informations but without success :
#JsonTypeInfo(use= JsonTypeInfo.Id.NAME, include= JsonTypeInfo.As.WRAPPER_OBJECT)
or #JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") (ko)
Do you have some idea?
Thanks
[UPDATE]
An another use case that work very well :
public abstract class AbstractSyncableWriteAndReadResource<T extends EntitySyncableWrite, K> ... {
public ResponseEntity<List<SyncQueuDTO<T>>> syncPushPut( #RequestBody SyncQueuDTO<T> data) {
...
}
}
But don't work when add RequestBody as List :
public abstract class AbstractSyncableWriteAndReadResource<T extends EntitySyncableWrite, K> ... {
public ResponseEntity<List<SyncQueuDTO<T>>> syncPushPut( #RequestBody List<SyncQueuDTO<T>> data) {
...
}
}
Exception :
Type definition error: [simple type, class xx.xx.xx.EntitySyncableWrite]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `xx.xx.xx.EntitySyncableWrite` (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information\n at [Source: (PushbackInputStream); line: 5, column: 26] (through reference chain: java.util.ArrayList[0]->xx.xx.xx.SyncQueuDTO[\"serverVersion\"])
With Map JSON array of objects to #RequestBody List<T> using jackson inspiration I solved this Jackson/JVM limitation as :
public abstract class AbstractSyncableWriteAndReadResource<T extends EntitySyncableWrite, K> extends AbstractSyncableReadResource<T, K> {
...
public ResponseEntity<List<SyncQueuDTO<T>>> syncPushPut(#RequestBody SyncQueuDTOList<T> queuList) {
....
}
}
and ArrayList implementation :
public class SyncQueuDTOList<T extends EntitySyncableWrite> extends ArrayList<SyncQueuDTO<T>> {}
When I try to return Map<String, List<Map<String, String>>> from the RestController, I get the following error:
"message": "Could not write JSON: Class java.util.ArrayList not subtype of [map type; class java.util.Map, [simple type, class java.lang.String] -> [simple type, class java.lang.Object]]; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Class java.util.ArrayList not subtype of [map type; class java.util.Map, [simple type, class java.lang.String] -> [simple type, class java.lang.Object]]
Can some help me with the solution?
What is the correct way to register a custom deserializer for a path variable in Spring web reactive?
Example:
#GetMapping("test/{customType}")
public String test(#PathVariable CustomType customType) { ...
I tried ObjectMapperBuilder, ObjectMapper and directly via #JsonDeserialize(using = CustomTypeMapper.class) but it wont register:
Response status 500 with reason "Conversion not supported."; nested exception is org.springframework.beans.ConversionNotSupportedException: Failed to convert value of type 'java.lang.String' to required type '...CustomType'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type '...CustomType': no matching editors or conversion strategy found
For path variables deserialization you don't need to involve jackson, you can use org.springframework.core.convert.converter.Converter
For example:
#Component
public class StringToLocalDateTimeConverter
implements Converter<String, LocalDateTime> {
#Override
public LocalDateTime convert(String source) {
return LocalDateTime.parse(
source, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
}
}
#GetMapping("/findbydate/{date}")
public GenericEntity findByDate(#PathVariable("date") LocalDateTime date) {
return ...;
}
Here's an article with examples
I ended up using #ModelValue because it semantically is not a deserialization, just parsing a key.
#RestController
public class FooController {
#GetMapping("test/{customType}")
public String test(#ModelAttribute CustomType customType) { ... }
}
#ControllerAdvice
public class GlobalControllerAdvice {
#ModelAttribute("customType")
public CustomType getCustomType(#PathVariable String customType) {
CustomeType result = // map value to object
return result;
}
}
I'm currently trying to serialize and deserialize a custom Map type as JSON using Jackson 2.8.4. I've managed to get serialization working based on this answer, but I'm struggling with deserialization. Here is an example:
public class TestMapSerialize {
public interface TestMapGetters {
Map<String, String> getFooMap();
Map<String, String> getBarMap();
}
#JsonSerialize(as = TestMapGetters.class)
public static class TestMap extends ForwardingMap<String, String>
implements TestMapGetters {
private Map<String, String> fooMap;
private Map<String, String> barMap;
#Override
protected Map<String, String> delegate() {
return ImmutableMap.<String, String>builder()
.putAll(fooMap)
.putAll(barMap)
.build();
}
// Getters and setters...
}
public static void main(String[] args) throws IOException {
final ObjectMapper mapper = new ObjectMapper();
final TestMap map = new TestMap();
map.setFooMap(ImmutableMap.of("a", "b", "c", "d"));
map.setBarMap(ImmutableMap.of("e", "f", "g", "h"));
final String json = mapper.writeValueAsString(map);
// Prints {"fooMap":{"a":"b","c":"d"},"barMap":{"e":"f","g":"h"}} as expected.
System.out.println(json);
// Throws JsonMappingException, see below.
final TestMap copy = mapper.readValue(json, TestMap.class);
}
}
When I try to deserialize as above, I get an exception:
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.lang.String out of START_OBJECT token
at [Source: {"fooMap":{"a":"b","c":"d"},"barMap":{"e":"f","g":"h"}}; line: 1, column: 11] (through reference chain: test.TestMapSerialize$TestMap["fooMap"])
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:270)
at com.fasterxml.jackson.databind.DeserializationContext.reportMappingException(DeserializationContext.java:1234)
at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1122)
at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1075)
at com.fasterxml.jackson.databind.deser.std.StringDeserializer.deserialize(StringDeserializer.java:60)
at com.fasterxml.jackson.databind.deser.std.StringDeserializer.deserialize(StringDeserializer.java:11)
at com.fasterxml.jackson.databind.deser.std.MapDeserializer._readAndBindStringKeyMap(MapDeserializer.java:517)
at com.fasterxml.jackson.databind.deser.std.MapDeserializer.deserialize(MapDeserializer.java:362)
at com.fasterxml.jackson.databind.deser.std.MapDeserializer.deserialize(MapDeserializer.java:27)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3798)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2842)
at test.TestMapSerialize.main(TestMapSerialize.java:66)
So Jackson is still trying to deseralize my class as though it's a generic Map, which won't work because it's a bean. I've tried adding #JsonDeserialize(as = TestMapGetters.class) as well, but then I get:
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Failed to narrow type [map type; class test.TestMapSerialize$TestMap, [simple type, class java.lang.String] -> [simple type, class java.lang.String]] with annotation (value test.TestMapSerialize$TestMapGetters), from 'test.TestMapSerialize$TestMap': Class test.TestMapSerialize$TestMapGetters not subtype of [map type; class test.TestMapSerialize$TestMap, [simple type, class java.lang.String] -> [simple type, class java.lang.String]]
at com.fasterxml.jackson.databind.AnnotationIntrospector.refineDeserializationType(AnnotationIntrospector.java:1194)
at com.fasterxml.jackson.databind.deser.DeserializerCache.modifyTypeByAnnotation(DeserializerCache.java:519)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer(DeserializerCache.java:333)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:264)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCacheValueDeserializer(DeserializerCache.java:244)
at com.fasterxml.jackson.databind.deser.DeserializerCache.findValueDeserializer(DeserializerCache.java:142)
at com.fasterxml.jackson.databind.DeserializationContext.findRootValueDeserializer(DeserializationContext.java:476)
at com.fasterxml.jackson.databind.ObjectMapper._findRootDeserializer(ObjectMapper.java:3899)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3794)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2842)
at test.TestMapSerialize.main(TestMapSerialize.java:68)
Caused by: java.lang.IllegalArgumentException: Class test.TestMapSerialize$TestMapGetters not subtype of [map type; class test.TestMapSerialize$TestMap, [simple type, class java.lang.String] -> [simple type, class java.lang.String]]
at com.fasterxml.jackson.databind.type.TypeFactory.constructSpecializedType(TypeFactory.java:359)
at com.fasterxml.jackson.databind.AnnotationIntrospector.refineDeserializationType(AnnotationIntrospector.java:1192)
... 10 more
which makes sense, because it's not a subtype. I'd rather not have to write a custom deserializer, since this would deserialize fine if my object weren't a subtype of Map. Is there any way I can tell Jackson to ignore the fact that my class is a Map and just deserialize as a bean?
I figured out that I can add a static creator method to my TestMap class:
#JsonCreator
static TestMap fromJson(
#JsonProperty("fooMap") ImmutableMap<String, String> fooMap,
#JsonProperty("barMap") ImmutableMap<String, String> barMap) {
final TestMap map = new TestMap();
map.setFooMap(fooMap);
map.setBarMap(barMap);
return map;
}
It would still be nice to have a way to say "just serialize and deserialize as a POJO" to avoid the whole #JsonCreator and #JsonSerialize(as) rigamarole.
Java :
/**
*/
#JsonTypeInfo(include=As.PROPERTY, property="_type", use=Id.NAME)
#JsonSubTypes({
#JsonSubTypes.Type(value=Dog.class, name="dog"),
#JsonSubTypes.Type(value=Cat.class, name="cat"),
})
#ResourceName("animal");
public interface AnimalDto {
/**
* Stop gap property to deal with jackson not serializing type information
* when using {#link JsonTypeInfo}
*
* #return A string name of the type of Animal
*/
#JsonProperty("_type")
}
public abstract class WildAnimal implements AnimalDto {
private final String type;
#Override
public String getType(){
return this.type;
}
}
#JsonTypeName("dog")
public class Dog implements WildAnimal {
}
#JsonTypeName("cat")
public class Cat implements WildAnimal {
}
JSON :
{
"animal": {
"_type":"dog",
"id": "1",
}
}
When I am trying to deserialize above json response to java, jackson throws the below error. Can someone please help me resolve this issue.
org.springframework.http.converter.HttpMessageNotReadableException: Could not read JSON: Unexpected token (END_OBJECT), expected FIELD_NAME: missing property '_type' that is to contain type id (for class Animal)
at [Source: sun.net.www.protocol.http.HttpURLConnection$HttpInputStream#4b10fe3f; line: 1, column: 111]; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Unexpected token (END_OBJECT), expected FIELD_NAME: missing property '_type' that is to contain type id (for class Animal)
at [Source: sun.net.www.protocol.http.HttpURLConnection$HttpInputStream#4b10fe3f; line: 1, column: 111]
at org.springframework.http.converter.json.MappingJackson2HttpMessageConverter.readJavaType(MappingJackson2HttpMessageConverter.java:228)
at org.springframework.http.converter.json.MappingJackson2HttpMessageConverter.read(MappingJackson2HttpMessageConverter.java:220)
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:95)
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:795)
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:779)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:559)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:512)
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:454)
You can see this post I think you'll find what you want :
Jackson JSON Polymorphism
PS : be carreful, you sometime have "_type" and sometime "type".