Reputation: 4524
I have the following class
public class BetWrapper {
private String description;
private Calendar startTime;
private HashMap<String, SimpleEntry<Integer, Double>> map;
public BetWrapper() {
map = new HashMap<>();
}
public Calendar getStartTime() {
return startTime;
}
public void setStartTime(Calendar startTime) {
this.startTime = startTime;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public HashMap<String, SimpleEntry<Integer, Double>> getMap() {
return map;
}
public void setMap(HashMap<String, SimpleEntry<Integer, Double>> map) {
this.map = map;
}
}
And I'm using JSONUtil class
public class JSONUtil {
private JSONUtil() {}
public static <T> T fromJSON(String content, Class<T> clazz) throws TechnicalException {
try {
return new ObjectMapper().readValue(content, clazz);
} catch (IOException e) {
throw new TechnicalException(e);
}
}
public static String toJSON(Object obj) throws TechnicalException {
try {
return new ObjectMapper().writeValueAsString(obj);
} catch (JsonProcessingException ex) {
throw new TechnicalException(ex);
}
}
}
I want to deserialzize a JSON to an BetWrapper object. But the following code produces some exceptions.
BetWrapper betWrapper = new BetWrapper();
betWrapper.setDescription("Stoke City - Arsenal");
betWrapper.setStartTime(Calendar.getInstance());
HashMap<String, AbstractMap.SimpleEntry<Integer, Double>> map = new HashMap<>();
map.put("home_team", new AbstractMap.SimpleEntry<>(1, 2.85));
betWrapper.setMap(map);
String json = JSONUtil.toJSON(betWrapper);
JSONUtil.fromJSON(json, BetWrapper.class);
The exceptions are:
Caused by: com.fasterxml.jackson.databind.JsonMappingException: No suitable constructor found for type [simple type, class java.util.AbstractMap$SimpleEntry<java.lang.Integer,java.lang.Double>]: can not instantiate from JSON object (need to add/enable type information?)
at [Source: {"description":"Stoke City - Arsenal","startTime":1417648132139,"map":{"home_team":"key":1,"value":2.85}}}; line: 1, column: 85] (through reference chain: by.bsu.kolodyuk.bettingapp.model.entity.BetWrapper["map"])
How to deserialize it correctly? It seems like that the problem is that types K,V in SimpleEntry should be specified for Jackson in some way.
Any Ideas?
Upvotes: 2
Views: 4174
Reputation: 319
In Jackson, you can define custom deserializers. So for your case, it may look like this:
import java.io.IOException;
import java.util.AbstractMap;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
public class SomeDeserializer extends StdDeserializer<AbstractMap.SimpleEntry>
{
public SomeDeserializer()
{
super( AbstractMap.SimpleEntry.class );
}
@Override
public AbstractMap.SimpleEntry deserialize( JsonParser jp, DeserializationContext ctxt ) throws IOException, JsonProcessingException
{
Integer key = null;
Double value = null;
JsonToken token;
while ( ( token = jp.nextValue() ) != null )
{
if ( token.isNumeric() )
{
String propertyName = jp.getCurrentName();
if ( "key".equalsIgnoreCase( propertyName ) )
{
key = jp.getIntValue();
}
else if ( "value".equalsIgnoreCase( propertyName ) )
{
value = jp.getDoubleValue();
}
}
}
if ( key != null && value != null )
{
return new AbstractMap.SimpleEntry( key, value );
}
return null;
}
}
Deserializers should be registered using ObjectMapper.registerModule(Module m)
. In your case, you can do it in your JSONUtil
utility class:
SimpleModule module = new SimpleModule();
module.addDeserializer( AbstractMap.SimpleEntry.class, new SomeDeserializer() );
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule( module );
Note that deserializer is registered with ObjectMapper
instance. Thus you better store the instance as a field of your utility class.
The deserializer class above is not comprehensive! This is just for demonstration for the case in hand. Further optimizations and refactorings can be applied as needed.
Upvotes: 0
Reputation: 279880
The type SimpleEntry
has the following constructor
public SimpleEntry(K key, V value) {
this.key = key;
this.value = value;
}
By default, Jackson expects a parameterless constructor. If such a constructor doesn't exist, it looks for one with @JsonProperty
annotations. (I might have this backwards, but don't ever code like that.) Since you're working with a JDK type, it obviously won't have those annotations.
You can use Mixins. Jackson uses these as templates to deserialize your target type.
abstract class Mixin<K, V> {
public Mixin(@JsonProperty("key") K key, @JsonProperty("value") V value) {}
}
...
public static <T> T fromJSON(String content, Class<T> clazz) throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.addMixInAnnotations(SimpleEntry.class, Mixin.class);
return mapper.readValue(content, clazz);
}
Jackson will use the meta type Mixin
, to deserialize to SimpleEntry
objects.
(Note, the type parameters of Mixin
and the constructor parameter types don't really matter. It's the fact that there are two constructor parameters and that the constructor parameters are annotated that matters.)
Upvotes: 4