I have a Jackson Question.
Is there a way to deserialize a property that may have two types, for some objects it appears like this
"someObj" : { "obj1" : 5, etc....}
then for others it appears as an empty array, i.e.
"someObj" : []
Any help is appreciated!
Thanks!
We can easily deserialize JSON Array into Java Array by using the readValue() method of the ObectMapper class. In the readValue() method, we pass two parameters, i.e., jsonString and Student[]. class.
Jackson uses default (no argument) constructor to create object and then sets value using setters. so you only need @NoArgsConstructor and @Setter.
Jackson mapper fill the ArrayList maintaining the order of JSON. If you want a different order you can use the annotation @JsonPropertyOrder.
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:
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.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With