Jackson deserialize object or array
Edit: Since Jackson 2.5.0, you can use DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_EMPTY_OBJECT to resolve your problem.
The solution Bruce provides has a few problems/disadvantages:
- you'll need to duplicate that code for each type you need to deserialize that way
- ObjectMapper should be reused since it caches serializers and deserializers and, thus, is expensive to create. See http://wiki.fasterxml.com/JacksonBestPracticesPerformance
- if your array contains some values, you probably want let jackson to fail deserializing it because it means there was a problem when it got encoded and you should see and fix that asap.
Here is my "generic" solution for that problem:
public abstract class EmptyArrayAsNullDeserializer<T> extends JsonDeserializer<T> {
private final Class<T> clazz;
protected EmptyArrayAsNullDeserializer(Class<T> clazz) {
this.clazz = clazz;
}
@Override
public T deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
ObjectCodec oc = jp.getCodec();
JsonNode node = oc.readTree(jp);
if (node.isArray() && !node.getElements().hasNext()) {
return null;
}
return oc.treeToValue(node, clazz);
}
}
then you still need to create a custom deserializer for each different type, but that's a lot easier to write and you don't duplicate any logic:
public class Thing2Deserializer extends EmptyArrayAsNullDeserializer<Thing2> {
public Thing2Deserializer() {
super(Thing2.class);
}
}
then you use it as usual:
@JsonDeserialize(using = Thing2Deserializer.class)
If you find a way to get rid of that last step, i.e. implementing 1 custom deserializer per type, I'm all ears ;)
Jackson doesn't currently have a built-in configuration to automatically handle this particular case, so custom deserialization processing is necessary.
Following is an example of what such custom deserialization might look like.
import java.io.IOException;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.Version;
import org.codehaus.jackson.annotate.JsonAutoDetect.Visibility;
import org.codehaus.jackson.annotate.JsonMethod;
import org.codehaus.jackson.map.DeserializationContext;
import org.codehaus.jackson.map.JsonDeserializer;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.module.SimpleModule;
public class JacksonFoo
{
public static void main(String[] args) throws Exception
{
// {"property1":{"property2":42}}
String json1 = "{\"property1\":{\"property2\":42}}";
// {"property1":[]}
String json2 = "{\"property1\":[]}";
SimpleModule module = new SimpleModule("", Version.unknownVersion());
module.addDeserializer(Thing2.class, new ArrayAsNullDeserializer());
ObjectMapper mapper = new ObjectMapper().setVisibility(JsonMethod.FIELD, Visibility.ANY).withModule(module);
Thing1 firstThing = mapper.readValue(json1, Thing1.class);
System.out.println(firstThing);
// output:
// Thing1: property1=Thing2: property2=42
Thing1 secondThing = mapper.readValue(json2, Thing1.class);
System.out.println(secondThing);
// output:
// Thing1: property1=null
}
}
class Thing1
{
Thing2 property1;
@Override
public String toString()
{
return String.format("Thing1: property1=%s", property1);
}
}
class Thing2
{
int property2;
@Override
public String toString()
{
return String.format("Thing2: property2=%d", property2);
}
}
class ArrayAsNullDeserializer extends JsonDeserializer<Thing2>
{
@Override
public Thing2 deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException
{
JsonNode node = jp.readValueAsTree();
if (node.isObject())
return new ObjectMapper().setVisibility(JsonMethod.FIELD, Visibility.ANY).readValue(node, Thing2.class);
return null;
}
}
(You could make use of DeserializationConfig.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY to force the input to always bind to a collection, but that's probably not the approach I'd take given how the problem is currently described.)