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.
Related
Im trying to Deserialize a hashmap using jackson. My data structure is
public class FilterDto {
private Map<String, List<String>> sectorFilter;
public FilterDto() {}
public Map<String, List<String>> getSectorFilter() {
return sectorFilter;
}
public void setSectorFilter(Map<String, List<String>> sectorFilter) {
this.sectorFilter = sectorFilter;
}
}
My request body is
{
"sectorFilter": [
{
"ab": [
"abc",
"cde"
]
}
]
}
When i tried to deserialize this i got
Cannot deserialize instance of `java.util.Map<java.lang.String,java.util.List<java.lang.String>>` out of START_ARRAY token
So i tried using a custom deserialzer to see whether it would work. I created a custom deerialzer like below
public class FilterDeserializer extends StdDeserializer<Map<String,List<String>>> {
public FilterDeserializer(StdDeserializer<?> src) {
super(src);
}
#Override
public Map<String,List<String>> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
}
}
and added it to my dto
#JsonDeserialize(using = FilterDeserializer.class)
private Map<String, List<String>> sectorFilter;
but now im getting
No qualifying bean of type 'com.fasterxml.jackson.databind.deser.std.StdDeserializer<?>' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
any idea how can i get this work ? Thanks
sectorFilter, as you’ve shown, isn’t a map, it’s an array of JSON objects. Try to deserialize as List<Map<String, List<String>>>. Of course, you can also create classes mimicking that structure if you want.
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>> {}
I am mapping the linkedHashMap to my Custom Pojo class using the below code.
ObjectMapper mapper = new ObjectMapper();**mapper.registerModule(new ParameterNamesModule()).registerModule(new Jdk8Module()).registerModule(new JavaTimeModule());** mapper.findAndRegisterModules(); mapper.convertValue(wrapper.getObject(), wrapper.getClassType());
This is giving me the below exception
"com.fasterxml.jackson.databind.JsonMappingException: Expected type float, integer, or string."
Previously, It was giving me a different exception(com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of java.time.Instant: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?)) and after adding the highlighted code to the mapper then it started giving this exception. Could anyone help me figure out how to solve this exception?
I have the following test which works for me on Jackson 2.8.9.
public class FooTest {
public static class CustomBean implements Serializable {
private static final long serialVersionUID = 1L;
#JsonInclude(JsonInclude.Include.NON_DEFAULT)
public Instant time;
public String recordName;
public CustomBean() {
}
#Override
public String toString() {
return String.format("name:%s time:%s", recordName, time);
}
}
#Test
public void test_me() throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.findAndRegisterModules();
Map<String, Object> data = new LinkedHashMap<>();
data.put("recordName", "test");
data.put("time", Instant.now());
String json = mapper.writeValueAsString(data);
System.out.println(json);
CustomBean bean = mapper.convertValue(data, CustomBean.class);
System.out.println(bean);
}
}
The output I get is:
{"recordName":"test","time":1536738977.085000000}
name:test time:2018-09-12T07:56:17.085Z
Comparing this to your JSON output in the comment, it feels like your Instant is not being serialised correctly. Even though you are loading the JavaTimeModule so I don't really know why that is happening.
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
}
}
When running the following test app
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.impl.StdTypeResolverBuilder;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) throws IOException {
// Create test data
Data data = new Data();
data.key = 1;
Map<String, Object> mapData = new HashMap<>();
mapData.put("longInMap", 2L);
mapData.put("longAsField", data);
// Configure Jackson to preserve types
JsonFactory factory = new JsonFactory();
ObjectMapper mapper = new ObjectMapper(factory);
StdTypeResolverBuilder resolver = new StdTypeResolverBuilder();
resolver.init(JsonTypeInfo.Id.CLASS, null);
resolver.inclusion(JsonTypeInfo.As.PROPERTY);
resolver.typeProperty("__t");
mapper.setDefaultTyping(resolver);
mapper.enable(SerializationFeature.INDENT_OUTPUT);
// Serialize
String json = mapper.writeValueAsString(mapData);
System.out.println("json = " + json);
// Deserialize
Map deserializedData = mapper.readValue(json, Map.class);
}
static class Data {
public long key;
}
}
I get this output and exception
json = {
"__t" : "java.util.HashMap",
"longInMap" : [ "java.lang.Long", 2 ],
"longAsField" : {
"__t" : "com.pinkmatter.bean.serialization.Main$Data",
"key" : [ "java.lang.Long", 1 ]
}
}
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Class java.lang.Long not subtype of [simple type, class long] (through reference chain: java.util.HashMap["longAsField"]->com.pinkmatter.bean.serialization.Data["key"])
at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:379)
at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:339)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.wrapAndThrow(BeanDeserializerBase.java:1591)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:278)
...
Caused by: java.lang.IllegalArgumentException: Class java.lang.Long not subtype of [simple type, class long]
at com.fasterxml.jackson.databind.type.TypeFactory.constructSpecializedType(TypeFactory.java:359)
at com.fasterxml.jackson.databind.jsontype.impl.ClassNameIdResolver._typeFromId(ClassNameIdResolver.java:72)
at com.fasterxml.jackson.databind.jsontype.impl.ClassNameIdResolver.typeFromId(ClassNameIdResolver.java:42)
...
I am trying to serialize a bunch of basic plain old java objects from libraries we are using (so we cannot modify the classes or add annotations), while also trying to preserve the types of values in collections ("longInMap" in the above example must stay a Long object).
The problem is that Jackson throws the above exception when trying to deserialize the primitive public long key in the Data class. If I change the type to public int key then no exception is thrown and deserialization works.
Also, since there are many different types of objects and I don't know at compile time exactly what will be serialized I don't think using mix-ins will work.
Please advise about what I might be doing wrong or of a possible workaround for getting primitive long fields deserialized while also maintaining the types of objects in collections.
I am using Jackson 2.8.3.
A workaround was provided here https://github.com/FasterXML/jackson-databind/issues/1395
The workaround is to replace
StdTypeResolverBuilder resolver = new StdTypeResolverBuilder();
with
StdTypeResolverBuilder resolver = new StdTypeResolverBuilder() {
#Override
public TypeSerializer buildTypeSerializer(SerializationConfig config, JavaType baseType, Collection<NamedType> subtypes) {
if (baseType.isPrimitive()) {
return null;
}
return super.buildTypeSerializer(config, baseType, subtypes);
}
#Override
public TypeDeserializer buildTypeDeserializer(DeserializationConfig config, JavaType baseType, Collection<NamedType> subtypes) {
if (baseType.isPrimitive()) {
return null;
}
return super.buildTypeDeserializer(config, baseType, subtypes);
}
};
A fix was implemented and will be available in jackson-databind 2.8.4 after which the workaround will no longer be required.