问题
This JSON snippet should be mapped to a Java-Objects that contains a cars
field of type Map<String, Car>
and a bikes
field of type Map<String, Bike>
. Because bikes and cars can be empty strings in the JSON file, i need a custom deserializer (See this question).
{
"id" : "1234",
"name" : "John Doe",
"cars" : {
"Tesla Model S" : {
"color" : "silver",
"buying_date" : "2012-06-01"
},
"Toyota Yaris" : {
"color" : "blue",
"buying_date" : "2005-01-01"
}
},
"bikes" : {
"Bike 1" : {
"color" : "black"
},
"Bike 2" : {
"color" : "red"
}
}
}
I thought about having instances of a generic custom deserializer that can be returned by the createContextual(DeserializationConfig cfg, BeanProperty property)
method of a ContextualDeserializer
based on the BeanProperty parameter. The generic custom deserializer looks like this:
public class MapsGenericDeserializer<T> extends
JsonDeserializer<Map<String, T>> {
private ObjectMapper mapper; // ObjectMapper without special map deserializer
public MapsGenericDeserializer(ObjectMapper mapper) {
this.mapper = mapper;
}
@Override
public Map<String, T> deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
ObjectCodec codec = jp.getCodec();
JsonNode node = codec.readTree(jp);
if (!"".equals(node.getTextValue())) {
return mapper.readValue(node,
new TypeReference<Map<String, T>>() {});
}
return null; // Node was an empty string
}
}
The contextual serializer below does not work because casting from MapsGenericDeserializer<Car>
to JsonDeserializer<Map<String,?>>
is not possible. Maybe this is possible in newer Versions of Java, but it does not work on the Version of Android I am coding for. So how can I implement the desired behaviour?
public class MapsDeserializer extends JsonDeserializer<Map<String, ?>>
implements ContextualDeserializer<Map<String, ?>> {
private ObjectMapper mapper;
MapsGenericDeserializer<Car> carDeserializer = new MapsGenericDeserializer<Car>(mapper);
MapsGenericDeserializer<Bike> bikeDeserializer = new MapsGenericDeserializer<Bike>(mapper);
public MapsDeserializer(ObjectMapper mapper) {
this.mapper = mapper;
}
@Override
public JsonDeserializer<Map<String, ?>> createContextual(DeserializationConfig cfg,
BeanProperty property) throws JsonMappingException {
Class<?> targetClass = property.getType().containedType(1).getRawClass();
if(targetClass.equals(Car.class) {
return carDeserializer; // Type mismatch!
} else if (targetClass.equals(Bike.class)) {
return bikeDeserializer; // Type mismatch!
} else {
return this;
}
}
// ...
}
回答1:
Here's how I might approach it.
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.Version;
import org.codehaus.jackson.map.BeanProperty;
import org.codehaus.jackson.map.ContextualDeserializer;
import org.codehaus.jackson.map.DeserializationConfig;
import org.codehaus.jackson.map.DeserializationContext;
import org.codehaus.jackson.map.JsonDeserializer;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.module.SimpleModule;
public class Foo
{
public static void main(String[] args) throws Exception
{
EmptyStringAsMapDeserializer<Map<String, ?>> emptyStringAsMapDeserializer =
new EmptyStringAsMapDeserializer<Map<String, ?>>(null, new ObjectMapper());
SimpleModule module = new SimpleModule("ThingsDeserializer", Version.unknownVersion());
module.addDeserializer(Map.class, emptyStringAsMapDeserializer);
ObjectMapper mapper = new ObjectMapper().withModule(module);
Person person = mapper.readValue(new File("input.json"), Person.class);
System.out.println(mapper.writeValueAsString(person));
}
}
class Person
{
public int id;
public String name;
public Map<String, Car> cars;
public Map<String, Bike> bikes;
}
class Car
{
public String color;
public String buying_date;
}
class Bike
{
public String color;
}
class EmptyStringAsMapDeserializer<T>
extends JsonDeserializer<Map<String, ?>>
implements ContextualDeserializer<Map<String, ?>>
{
private Class<?> targetType;
private ObjectMapper mapper;
EmptyStringAsMapDeserializer(Class<?> targetType, ObjectMapper mapper)
{
this.targetType = targetType;
this.mapper = mapper;
}
@Override
public JsonDeserializer<Map<String, ?>> createContextual(DeserializationConfig config, BeanProperty property)
throws JsonMappingException
{
return new EmptyStringAsMapDeserializer<Object>(property.getType().containedType(1).getRawClass(), mapper);
}
@Override
public Map<String, ?> deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
JsonNode node = jp.readValueAsTree();
if ("".equals(node.getTextValue()))
return new HashMap<String, Object>();
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(node, mapper.getTypeFactory().constructMapType(Map.class, String.class, targetType));
}
}
The generic type parameters might be a little out of order. I did a bit of quick copy-pasting.
来源:https://stackoverflow.com/questions/6704992/contextualdeserializer-for-mapping-json-to-different-types-of-maps-with-jackson