diff --git a/release-notes/VERSION b/release-notes/VERSION index c071fcf870..d0129ead22 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -10,6 +10,8 @@ Project: jackson-databind #113: Problem deserializing polymorphic types with @JsonCreator #165: Add `DeserializationContext.getContextualType()` to let deserializer known the expected type. +#299: Add `DeserializationFeature.FAIL_ON_UNRESOLVED_OBJECT_IDS` to allow missing + Object Ids (as global default) #408: External type id does not allow use of 'visible=true' #421: @JsonCreator not used in case of multiple creators with parameter names (reported by Lovro P, lpandzic@github) diff --git a/src/main/java/com/fasterxml/jackson/databind/DeserializationFeature.java b/src/main/java/com/fasterxml/jackson/databind/DeserializationFeature.java index f048a4d155..6086743862 100644 --- a/src/main/java/com/fasterxml/jackson/databind/DeserializationFeature.java +++ b/src/main/java/com/fasterxml/jackson/databind/DeserializationFeature.java @@ -172,6 +172,22 @@ public enum DeserializationFeature implements ConfigFeature */ FAIL_ON_IGNORED_PROPERTIES(false), + /** + * Feature that determines what happens if an Object Id reference is encountered + * that does not refer to an actual Object with that id ("unresolved Object Id"): + * either an exception is thrown (true), or a null object is used + * instead (false). + * Note that if this is set to false, no further processing is done; + * specifically, if reference is defined via setter method, that method will NOT + * be called. + *

+ * Feature is enabled by default, so that unknown Object Ids will result in an + * exception being thrown, at the end of deserialization. + * + * @since 2.5 + */ + FAIL_ON_UNRESOLVED_OBJECT_IDS(true), + /** * Feature that determines whether Jackson code should catch * and wrap {@link Exception}s (but never {@link Error}s!) @@ -188,7 +204,7 @@ public enum DeserializationFeature implements ConfigFeature * Feature is enabled by default. */ WRAP_EXCEPTIONS(true), - + /* /****************************************************** /* Structural conversion features diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/DefaultDeserializationContext.java b/src/main/java/com/fasterxml/jackson/databind/deser/DefaultDeserializationContext.java index 72ecc78aa0..fd579391dd 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/DefaultDeserializationContext.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/DefaultDeserializationContext.java @@ -138,7 +138,10 @@ public void checkUnresolvedObjectId() throws UnresolvedForwardReference if (_objectIds == null) { return; } - + // 29-Dec-2014, tatu: As per [databind#299], may also just let unresolved refs be... + if (!isEnabled(DeserializationFeature.FAIL_ON_UNRESOLVED_OBJECT_IDS)) { + return; + } UnresolvedForwardReference exception = null; for (Entry entry : _objectIds.entrySet()) { ReadableObjectId roid = entry.getValue(); @@ -146,7 +149,7 @@ public void checkUnresolvedObjectId() throws UnresolvedForwardReference if (exception == null) { exception = new UnresolvedForwardReference("Unresolved forward references for: "); } - for (Iterator iterator = roid.referringProperties(); iterator.hasNext();) { + for (Iterator iterator = roid.referringProperties(); iterator.hasNext(); ) { Referring referring = iterator.next(); exception.addUnresolvedId(roid.getKey().key, referring.getBeanType(), referring.getLocation()); } diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/ContainerSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/ContainerSerializer.java index ac08200969..119a2d8545 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/ContainerSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/ContainerSerializer.java @@ -84,18 +84,25 @@ public ContainerSerializer withValueTypeSerializer(TypeSerializer vts) { * {@link com.fasterxml.jackson.databind.SerializerProvider#findValueSerializer}. */ public abstract JsonSerializer getContentSerializer(); - + /* /********************************************************** /* Abstract methods for sub-classes to implement /********************************************************** */ - + /* Overridden as abstract, to force re-implementation; necessary for all * collection types. */ @Override - public abstract boolean isEmpty(T value); + @Deprecated + public boolean isEmpty(T value) { + return isEmpty(null, value); + } + + // since 2.5: should be declared abstract in future (2.6) +// @Override +// public abstract boolean isEmpty(SerializerProvider prov, T value); /** * Method called to determine if the given value (of type handled by diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedListSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedListSerializer.java index a7ff0f50f9..346c2cc6d9 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedListSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedListSerializer.java @@ -47,7 +47,7 @@ public IndexedListSerializer withResolved(BeanProperty property, */ @Override - public boolean isEmpty(List value) { + public boolean isEmpty(SerializerProvider prov, List value) { return (value == null) || value.isEmpty(); } diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/IteratorSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/IteratorSerializer.java index 0a77d721de..77737b1458 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/IteratorSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/IteratorSerializer.java @@ -28,7 +28,7 @@ public IteratorSerializer(IteratorSerializer src, } @Override - public boolean isEmpty(Iterator value) { + public boolean isEmpty(SerializerProvider prov, Iterator value) { return (value == null) || !value.hasNext(); } diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/MapEntrySerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/MapEntrySerializer.java index 165ebbe08f..e987cd5e49 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/MapEntrySerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/MapEntrySerializer.java @@ -174,7 +174,7 @@ public boolean hasSingleElement(Map.Entry value) { } @Override - public boolean isEmpty(Entry value) { + public boolean isEmpty(SerializerProvider prov, Entry value) { return (value == null); } diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/StringArraySerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/StringArraySerializer.java index d64c8c8cf0..72e88b0fb7 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/StringArraySerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/StringArraySerializer.java @@ -130,7 +130,7 @@ public JsonSerializer getContentSerializer() { } @Override - public boolean isEmpty(String[] value) { + public boolean isEmpty(SerializerProvider prov, String[] value) { return (value == null) || (value.length == 0); } diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/CollectionSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/CollectionSerializer.java index 1b32c344b3..09bc66ba49 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/CollectionSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/CollectionSerializer.java @@ -62,7 +62,7 @@ public CollectionSerializer withResolved(BeanProperty property, */ @Override - public boolean isEmpty(Collection value) { + public boolean isEmpty(SerializerProvider prov, Collection value) { return (value == null) || value.isEmpty(); } diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/EnumMapSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/EnumMapSerializer.java index 48a2c80ac5..84a5b4fd95 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/EnumMapSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/EnumMapSerializer.java @@ -162,7 +162,7 @@ public JsonSerializer getContentSerializer() { } @Override - public boolean isEmpty(EnumMap,?> value) { + public boolean isEmpty(SerializerProvider prov, EnumMap,?> value) { return (value == null) || value.isEmpty(); } diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/EnumSetSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/EnumSetSerializer.java index cf0487cd91..5a63d20986 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/EnumSetSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/EnumSetSerializer.java @@ -35,7 +35,7 @@ public EnumSetSerializer withResolved(BeanProperty property, } @Override - public boolean isEmpty(EnumSet> value) { + public boolean isEmpty(SerializerProvider prov, EnumSet> value) { return (value == null) || value.isEmpty(); } diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/IterableSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/IterableSerializer.java index 3879d9b2fa..7b3f9e0e09 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/IterableSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/IterableSerializer.java @@ -38,7 +38,7 @@ public IterableSerializer withResolved(BeanProperty property, } @Override - public boolean isEmpty(Iterable value) { + public boolean isEmpty(SerializerProvider prov, Iterable value) { // Not really good way to implement this, but has to do for now: return (value == null) || !value.iterator().hasNext(); } diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/MapSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/MapSerializer.java index 1029f3474b..ce020d157f 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/MapSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/MapSerializer.java @@ -401,7 +401,7 @@ public JsonSerializer getContentSerializer() { } @Override - public boolean isEmpty(Map value) { + public boolean isEmpty(SerializerProvider prov, Map value) { return (value == null) || value.isEmpty(); } diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/ObjectArraySerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/ObjectArraySerializer.java index 7579345ace..ad76e7c6d9 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/ObjectArraySerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/ObjectArraySerializer.java @@ -174,7 +174,7 @@ public JsonSerializer getContentSerializer() { } @Override - public boolean isEmpty(Object[] value) { + public boolean isEmpty(SerializerProvider prov, Object[] value) { return (value == null) || (value.length == 0); } @@ -190,8 +190,7 @@ public boolean hasSingleElement(Object[] value) { */ @Override - public final void serialize(Object[] value, JsonGenerator jgen, SerializerProvider provider) - throws IOException, JsonGenerationException + public final void serialize(Object[] value, JsonGenerator jgen, SerializerProvider provider) throws IOException { final int len = value.length; if ((len == 1) && provider.isEnabled(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED)) { @@ -204,8 +203,7 @@ public final void serialize(Object[] value, JsonGenerator jgen, SerializerProvid } @Override - public void serializeContents(Object[] value, JsonGenerator jgen, SerializerProvider provider) - throws IOException, JsonGenerationException + public void serializeContents(Object[] value, JsonGenerator jgen, SerializerProvider provider) throws IOException { final int len = value.length; if (len == 0) { @@ -262,8 +260,7 @@ public void serializeContents(Object[] value, JsonGenerator jgen, SerializerProv } public void serializeContentsUsing(Object[] value, JsonGenerator jgen, SerializerProvider provider, - JsonSerializer ser) - throws IOException, JsonGenerationException + JsonSerializer ser) throws IOException { final int len = value.length; final TypeSerializer typeSer = _valueTypeSerializer; @@ -297,8 +294,7 @@ public void serializeContentsUsing(Object[] value, JsonGenerator jgen, Serialize } } - public void serializeTypedContents(Object[] value, JsonGenerator jgen, SerializerProvider provider) - throws IOException, JsonGenerationException + public void serializeTypedContents(Object[] value, JsonGenerator jgen, SerializerProvider provider) throws IOException { final int len = value.length; final TypeSerializer typeSer = _valueTypeSerializer; diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/StdArraySerializers.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/StdArraySerializers.java index 798292059d..ef537b6f8a 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/StdArraySerializers.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/StdArraySerializers.java @@ -110,7 +110,7 @@ public JsonSerializer getContentSerializer() { } @Override - public boolean isEmpty(boolean[] value) { + public boolean isEmpty(SerializerProvider prov, boolean[] value) { return (value == null) || (value.length == 0); } @@ -249,7 +249,7 @@ public JsonSerializer getContentSerializer() { } @Override - public boolean isEmpty(short[] value) { + public boolean isEmpty(SerializerProvider prov, short[] value) { return (value == null) || (value.length == 0); } @@ -417,7 +417,7 @@ public JsonSerializer getContentSerializer() { } @Override - public boolean isEmpty(int[] value) { + public boolean isEmpty(SerializerProvider prov, int[] value) { return (value == null) || (value.length == 0); } @@ -494,7 +494,7 @@ public JsonSerializer getContentSerializer() { } @Override - public boolean isEmpty(long[] value) { + public boolean isEmpty(SerializerProvider prov, long[] value) { return (value == null) || (value.length == 0); } @@ -585,7 +585,7 @@ public JsonSerializer getContentSerializer() { } @Override - public boolean isEmpty(float[] value) { + public boolean isEmpty(SerializerProvider prov, float[] value) { return (value == null) || (value.length == 0); } @@ -670,7 +670,7 @@ public JsonSerializer getContentSerializer() { } @Override - public boolean isEmpty(double[] value) { + public boolean isEmpty(SerializerProvider prov, double[] value) { return (value == null) || (value.length == 0); } diff --git a/src/test/java/com/fasterxml/jackson/databind/struct/TestObjectIdDeserialization.java b/src/test/java/com/fasterxml/jackson/databind/struct/TestObjectIdDeserialization.java index 2df61ef0ed..171ffd715a 100644 --- a/src/test/java/com/fasterxml/jackson/databind/struct/TestObjectIdDeserialization.java +++ b/src/test/java/com/fasterxml/jackson/databind/struct/TestObjectIdDeserialization.java @@ -14,6 +14,7 @@ import com.fasterxml.jackson.annotation.ObjectIdResolver; import com.fasterxml.jackson.databind.BaseMapTest; import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.cfg.ContextAttributes; import com.fasterxml.jackson.databind.deser.UnresolvedForwardReference; @@ -341,6 +342,16 @@ public void testUnresolvedForwardReference() } } + // [databind#299]: Allow unresolved ids to become nulls + public void testUnresolvableAsNull() throws Exception + { + IdWrapper w = mapper.reader(IdWrapper.class) + .without(DeserializationFeature.FAIL_ON_UNRESOLVED_OBJECT_IDS) + .readValue(aposToQuotes("{'node':123}")); + assertNotNull(w); + assertNull(w.node); + } + public void testKeepCollectionOrdering() throws Exception { String json = "{\"employees\":[2,1,"