From 4349557c7b3138ecc62bf96270a7bbaecd8cfe07 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Fri, 5 Dec 2025 21:13:10 -0800 Subject: [PATCH 01/19] Fix #1654 --- .../jsontype/TypeResolverProvider.java | 16 +++ .../jsontype/impl/NoOpTypeDeserializer.java | 117 ++++++++++++++++++ .../jsontype/impl/NoOpTypeSerializer.java | 83 +++++++++++++ .../NoTypeInfo1654Test.java | 5 +- 4 files changed, 217 insertions(+), 4 deletions(-) create mode 100644 src/main/java/tools/jackson/databind/jsontype/impl/NoOpTypeDeserializer.java create mode 100644 src/main/java/tools/jackson/databind/jsontype/impl/NoOpTypeSerializer.java rename src/test/java/tools/jackson/databind/{tofix => jsontype}/NoTypeInfo1654Test.java (94%) diff --git a/src/main/java/tools/jackson/databind/jsontype/TypeResolverProvider.java b/src/main/java/tools/jackson/databind/jsontype/TypeResolverProvider.java index 8f9f3bd11f..4985050846 100644 --- a/src/main/java/tools/jackson/databind/jsontype/TypeResolverProvider.java +++ b/src/main/java/tools/jackson/databind/jsontype/TypeResolverProvider.java @@ -113,6 +113,10 @@ public TypeSerializer findPropertyTypeSerializer(SerializationContext ctxt, if (b == null) { return findTypeSerializer(ctxt, baseType, ctxt.introspectClassAnnotations(baseType)); } + // [databind#1654]: Explicit `@JsonTypeInfo(Id.NONE)` should block class-level type info + if (b == NO_RESOLVER) { + return null; + } Collection subtypes = config.getSubtypeResolver().collectAndResolveSubtypesByClass( config, accessor, baseType); // 10-Jun-2015, tatu: Since not created for Bean Property, no need for post-processing @@ -133,6 +137,10 @@ public TypeDeserializer findPropertyTypeDeserializer(DeserializationContext ctxt if (b == null) { return findTypeDeserializer(ctxt, baseType, ctxt.introspectClassAnnotations(baseType)); } + // [databind#1654]: Explicit `@JsonTypeInfo(Id.NONE)` should block class-level type info + if (b == NO_RESOLVER) { + return null; + } Collection subtypes = config.getSubtypeResolver().collectAndResolveSubtypesByTypeId(config, accessor, baseType); // May need to figure out default implementation, if none found yet @@ -162,6 +170,10 @@ public TypeSerializer findPropertyContentTypeSerializer(SerializationContext ctx return findTypeSerializer(ctxt, contentType, ctxt.introspectClassAnnotations(contentType.getRawClass())); } + // [databind#1654]: Explicit `@JsonTypeInfo(Id.NONE)` should block class-level type info + if (b == NO_RESOLVER) { + return tools.jackson.databind.jsontype.impl.NoOpTypeSerializer.instance(); + } Collection subtypes = config.getSubtypeResolver().collectAndResolveSubtypesByClass( config, accessor, contentType); return b.buildTypeSerializer(ctxt, contentType, subtypes); @@ -181,6 +193,10 @@ public TypeDeserializer findPropertyContentTypeDeserializer(DeserializationConte if (b == null) { return findTypeDeserializer(ctxt, contentType, ctxt.introspectClassAnnotations(contentType)); } + // [databind#1654]: Explicit `@JsonTypeInfo(Id.NONE)` should block class-level type info + if (b == NO_RESOLVER) { + return new tools.jackson.databind.jsontype.impl.NoOpTypeDeserializer(contentType); + } Collection subtypes = config.getSubtypeResolver().collectAndResolveSubtypesByTypeId(config, accessor, contentType); // May need to figure out default implementation, if none found yet diff --git a/src/main/java/tools/jackson/databind/jsontype/impl/NoOpTypeDeserializer.java b/src/main/java/tools/jackson/databind/jsontype/impl/NoOpTypeDeserializer.java new file mode 100644 index 0000000000..a0bee9b69c --- /dev/null +++ b/src/main/java/tools/jackson/databind/jsontype/impl/NoOpTypeDeserializer.java @@ -0,0 +1,117 @@ +package tools.jackson.databind.jsontype.impl; + +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +import tools.jackson.core.*; +import tools.jackson.databind.*; +import tools.jackson.databind.jsontype.TypeDeserializer; +import tools.jackson.databind.jsontype.TypeIdResolver; + +/** + * Special {@link TypeDeserializer} implementation used to explicitly + * block type deserialization. This is used when a property or class + * is annotated with {@code @JsonTypeInfo(use = Id.NONE)}, indicating + * that type information should not be expected or processed even if + * the value type has a class-level type info annotation. + *

+ * Unlike returning {@code null} (which means "no special type handling, + * use defaults"), this actively prevents type information from being read. + * + * @since 3.1 + */ +public class NoOpTypeDeserializer extends TypeDeserializer +{ + private final JavaType _baseType; + private final ValueDeserializer _defaultDeserializer; + + public NoOpTypeDeserializer(JavaType baseType) { + _baseType = baseType; + _defaultDeserializer = null; + } + + private NoOpTypeDeserializer(JavaType baseType, + ValueDeserializer defaultDeserializer) { + _baseType = baseType; + _defaultDeserializer = defaultDeserializer; + } + + public NoOpTypeDeserializer withDefaultImpl(ValueDeserializer deser) { + if (_defaultDeserializer == deser) { + return this; + } + return new NoOpTypeDeserializer(_baseType, deser); + } + + @Override + public TypeDeserializer forProperty(BeanProperty prop) { + return this; + } + + @Override + public JsonTypeInfo.As getTypeInclusion() { + return JsonTypeInfo.As.PROPERTY; + } + + @Override + public String getPropertyName() { + return null; + } + + @Override + public TypeIdResolver getTypeIdResolver() { + return null; + } + + @Override + public Class getDefaultImpl() { + return null; + } + + @Override + public Object deserializeTypedFromObject(JsonParser p, + DeserializationContext ctxt) throws JacksonException + { + // Just deserialize without type info + return _deserialize(p, ctxt); + } + + @Override + public Object deserializeTypedFromArray(JsonParser p, + DeserializationContext ctxt) throws JacksonException + { + // Just deserialize without type info + return _deserialize(p, ctxt); + } + + @Override + public Object deserializeTypedFromScalar(JsonParser p, + DeserializationContext ctxt) throws JacksonException + { + // Just deserialize without type info + return _deserialize(p, ctxt); + } + + @Override + public Object deserializeTypedFromAny(JsonParser p, + DeserializationContext ctxt) throws JacksonException + { + // Just deserialize without type info + return _deserialize(p, ctxt); + } + + protected Object _deserialize(JsonParser p, DeserializationContext ctxt) + throws JacksonException + { + if (_defaultDeserializer != null) { + return _defaultDeserializer.deserialize(p, ctxt); + } + // Find deserializer for the base type (this will find custom deserializers + // registered for this type, including those from @JsonDeserialize annotations) + ValueDeserializer deser = ctxt.findContextualValueDeserializer(_baseType, null); + if (deser == null) { + ctxt.reportBadDefinition(_baseType, + "Cannot find deserializer for type " + _baseType); + } + return deser.deserialize(p, ctxt); + } +} diff --git a/src/main/java/tools/jackson/databind/jsontype/impl/NoOpTypeSerializer.java b/src/main/java/tools/jackson/databind/jsontype/impl/NoOpTypeSerializer.java new file mode 100644 index 0000000000..75458a5de1 --- /dev/null +++ b/src/main/java/tools/jackson/databind/jsontype/impl/NoOpTypeSerializer.java @@ -0,0 +1,83 @@ +package tools.jackson.databind.jsontype.impl; + +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +import tools.jackson.core.*; +import tools.jackson.core.type.WritableTypeId; +import tools.jackson.databind.BeanProperty; +import tools.jackson.databind.SerializationContext; +import tools.jackson.databind.jsontype.TypeIdResolver; +import tools.jackson.databind.jsontype.TypeSerializer; + +/** + * Special {@link TypeSerializer} implementation used to explicitly + * block type serialization. This is used when a property or class + * is annotated with {@code @JsonTypeInfo(use = Id.NONE)}, indicating + * that type information should not be included even if the value type + * has a class-level type info annotation. + *

+ * Unlike returning {@code null} (which means "no special type handling, + * use defaults"), this actively prevents type information from being written. + * + * @since 3.1 + */ +public class NoOpTypeSerializer extends TypeSerializer +{ + private static final NoOpTypeSerializer INSTANCE = new NoOpTypeSerializer(); + + private NoOpTypeSerializer() { } + + public static NoOpTypeSerializer instance() { + return INSTANCE; + } + + @Override + public TypeSerializer forProperty(SerializationContext ctxt, BeanProperty prop) { + return this; + } + + @Override + public JsonTypeInfo.As getTypeInclusion() { + return JsonTypeInfo.As.PROPERTY; + } + + @Override + public String getPropertyName() { + return null; + } + + @Override + public TypeIdResolver getTypeIdResolver() { + return null; + } + + @Override + public WritableTypeId writeTypePrefix(JsonGenerator g, + SerializationContext ctxt, WritableTypeId typeId) + throws JacksonException + { + // Write the value start token if needed, but NO type information + if (typeId.valueShape == JsonToken.START_OBJECT) { + g.writeStartObject(typeId.forValue); + } else if (typeId.valueShape == JsonToken.START_ARRAY) { + g.writeStartArray(); + } + // Mark as "already written" to prevent suffix from trying to close + typeId.wrapperWritten = false; + return typeId; + } + + @Override + public WritableTypeId writeTypeSuffix(JsonGenerator g, + SerializationContext ctxt, WritableTypeId typeId) + throws JacksonException + { + // Write the value end token if needed, but NO type information + if (typeId.valueShape == JsonToken.START_OBJECT) { + g.writeEndObject(); + } else if (typeId.valueShape == JsonToken.START_ARRAY) { + g.writeEndArray(); + } + return typeId; + } +} diff --git a/src/test/java/tools/jackson/databind/tofix/NoTypeInfo1654Test.java b/src/test/java/tools/jackson/databind/jsontype/NoTypeInfo1654Test.java similarity index 94% rename from src/test/java/tools/jackson/databind/tofix/NoTypeInfo1654Test.java rename to src/test/java/tools/jackson/databind/jsontype/NoTypeInfo1654Test.java index 476811f895..82e046ff38 100644 --- a/src/test/java/tools/jackson/databind/tofix/NoTypeInfo1654Test.java +++ b/src/test/java/tools/jackson/databind/jsontype/NoTypeInfo1654Test.java @@ -1,4 +1,4 @@ -package tools.jackson.databind.tofix; +package tools.jackson.databind.jsontype; import java.util.*; @@ -10,7 +10,6 @@ import tools.jackson.databind.*; import tools.jackson.databind.annotation.JsonDeserialize; import tools.jackson.databind.testutil.DatabindTestUtil; -import tools.jackson.databind.testutil.failure.JacksonTestFailureExpected; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -77,7 +76,6 @@ void noTypeElementOverride() throws Exception { } // [databind#1654] - @JacksonTestFailureExpected @Test void noTypeInfoOverrideSer() throws Exception { Value1654UntypedContainer cont = new Value1654UntypedContainer( @@ -89,7 +87,6 @@ void noTypeInfoOverrideSer() throws Exception { } // [databind#1654] - @JacksonTestFailureExpected @Test void noTypeInfoOverrideDeser() throws Exception { // and then actual failing case From 57d8d920cd57e483dda5a6e93aedfef471b61857 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sat, 6 Dec 2025 09:58:22 -0800 Subject: [PATCH 02/19] Minor clean up --- release-notes/VERSION | 3 ++ .../jsontype/TypeResolverProvider.java | 6 ++-- .../jsontype/impl/NoOpTypeDeserializer.java | 32 ++++++++----------- .../jsontype/impl/NoOpTypeSerializer.java | 8 +++-- 4 files changed, 26 insertions(+), 23 deletions(-) diff --git a/release-notes/VERSION b/release-notes/VERSION index df98713858..44c020520c 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -11,6 +11,9 @@ Versions: 3.x (for earlier see VERSION-2.x) #1196: Add opt-in error collection for deserialization (requested by @odrotbohm) (contributed by @sri-adarsh-kumar) +#1654: @JsonDeserialize(contentUsing=...) is ignored if content + type is determined by @JsonTypeInfo + (reported by @pdegoeje) #1980: Add method `remove(JsonPointer)` in `ContainerNode` (fix by @cowtowncoder, w/ Claude code) #3964: Deserialization issue: MismatchedInputException, Bean not diff --git a/src/main/java/tools/jackson/databind/jsontype/TypeResolverProvider.java b/src/main/java/tools/jackson/databind/jsontype/TypeResolverProvider.java index 4985050846..cad5f6a727 100644 --- a/src/main/java/tools/jackson/databind/jsontype/TypeResolverProvider.java +++ b/src/main/java/tools/jackson/databind/jsontype/TypeResolverProvider.java @@ -9,6 +9,8 @@ import tools.jackson.databind.introspect.Annotated; import tools.jackson.databind.introspect.AnnotatedClass; import tools.jackson.databind.introspect.AnnotatedMember; +import tools.jackson.databind.jsontype.impl.NoOpTypeDeserializer; +import tools.jackson.databind.jsontype.impl.NoOpTypeSerializer; import tools.jackson.databind.jsontype.impl.StdTypeResolverBuilder; /** @@ -172,7 +174,7 @@ public TypeSerializer findPropertyContentTypeSerializer(SerializationContext ctx } // [databind#1654]: Explicit `@JsonTypeInfo(Id.NONE)` should block class-level type info if (b == NO_RESOLVER) { - return tools.jackson.databind.jsontype.impl.NoOpTypeSerializer.instance(); + return NoOpTypeSerializer.instance(); } Collection subtypes = config.getSubtypeResolver().collectAndResolveSubtypesByClass( config, accessor, contentType); @@ -195,7 +197,7 @@ public TypeDeserializer findPropertyContentTypeDeserializer(DeserializationConte } // [databind#1654]: Explicit `@JsonTypeInfo(Id.NONE)` should block class-level type info if (b == NO_RESOLVER) { - return new tools.jackson.databind.jsontype.impl.NoOpTypeDeserializer(contentType); + return new NoOpTypeDeserializer(contentType, null); } Collection subtypes = config.getSubtypeResolver().collectAndResolveSubtypesByTypeId(config, accessor, contentType); diff --git a/src/main/java/tools/jackson/databind/jsontype/impl/NoOpTypeDeserializer.java b/src/main/java/tools/jackson/databind/jsontype/impl/NoOpTypeDeserializer.java index a0bee9b69c..9308fe493b 100644 --- a/src/main/java/tools/jackson/databind/jsontype/impl/NoOpTypeDeserializer.java +++ b/src/main/java/tools/jackson/databind/jsontype/impl/NoOpTypeDeserializer.java @@ -22,21 +22,15 @@ public class NoOpTypeDeserializer extends TypeDeserializer { private final JavaType _baseType; - private final ValueDeserializer _defaultDeserializer; + private final ValueDeserializer _deserializer; - public NoOpTypeDeserializer(JavaType baseType) { + public NoOpTypeDeserializer(JavaType baseType, ValueDeserializer deser) { _baseType = baseType; - _defaultDeserializer = null; + _deserializer = deser; } - private NoOpTypeDeserializer(JavaType baseType, - ValueDeserializer defaultDeserializer) { - _baseType = baseType; - _defaultDeserializer = defaultDeserializer; - } - - public NoOpTypeDeserializer withDefaultImpl(ValueDeserializer deser) { - if (_defaultDeserializer == deser) { + public NoOpTypeDeserializer withDeserializer(ValueDeserializer deser) { + if (_deserializer == deser) { return this; } return new NoOpTypeDeserializer(_baseType, deser); @@ -49,7 +43,8 @@ public TypeDeserializer forProperty(BeanProperty prop) { @Override public JsonTypeInfo.As getTypeInclusion() { - return JsonTypeInfo.As.PROPERTY; + // No proper value but need to return something + return JsonTypeInfo.As.EXISTING_PROPERTY; } @Override @@ -102,15 +97,16 @@ public Object deserializeTypedFromAny(JsonParser p, protected Object _deserialize(JsonParser p, DeserializationContext ctxt) throws JacksonException { - if (_defaultDeserializer != null) { - return _defaultDeserializer.deserialize(p, ctxt); - } + ValueDeserializer deser = _deserializer; + // Find deserializer for the base type (this will find custom deserializers // registered for this type, including those from @JsonDeserialize annotations) - ValueDeserializer deser = ctxt.findContextualValueDeserializer(_baseType, null); if (deser == null) { - ctxt.reportBadDefinition(_baseType, - "Cannot find deserializer for type " + _baseType); + deser = ctxt.findContextualValueDeserializer(_baseType, null); + if (deser == null) { + ctxt.reportBadDefinition(_baseType, + "Cannot find deserializer for type " + _baseType); + } } return deser.deserialize(p, ctxt); } diff --git a/src/main/java/tools/jackson/databind/jsontype/impl/NoOpTypeSerializer.java b/src/main/java/tools/jackson/databind/jsontype/impl/NoOpTypeSerializer.java index 75458a5de1..0820ea70ea 100644 --- a/src/main/java/tools/jackson/databind/jsontype/impl/NoOpTypeSerializer.java +++ b/src/main/java/tools/jackson/databind/jsontype/impl/NoOpTypeSerializer.java @@ -38,7 +38,8 @@ public TypeSerializer forProperty(SerializationContext ctxt, BeanProperty prop) @Override public JsonTypeInfo.As getTypeInclusion() { - return JsonTypeInfo.As.PROPERTY; + // No proper one to use but must return something: + return JsonTypeInfo.As.EXISTING_PROPERTY; } @Override @@ -62,7 +63,8 @@ public WritableTypeId writeTypePrefix(JsonGenerator g, } else if (typeId.valueShape == JsonToken.START_ARRAY) { g.writeStartArray(); } - // Mark as "already written" to prevent suffix from trying to close + // 1. Start marker (part of value) was written but + // 2. No value wrapper was written. typeId.wrapperWritten = false; return typeId; } @@ -72,7 +74,7 @@ public WritableTypeId writeTypeSuffix(JsonGenerator g, SerializationContext ctxt, WritableTypeId typeId) throws JacksonException { - // Write the value end token if needed, but NO type information + // Write the value end token if needed, but no wrapper to close if (typeId.valueShape == JsonToken.START_OBJECT) { g.writeEndObject(); } else if (typeId.valueShape == JsonToken.START_ARRAY) { From 5845399ed902b1258fec401625c95ff19df923d6 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sat, 6 Dec 2025 10:20:53 -0800 Subject: [PATCH 03/19] Bigger PR rewrite --- .../jsontype/TypeResolverProvider.java | 2 +- .../jsontype/impl/NoOpTypeDeserializer.java | 53 ++++++++++--------- .../databind/jsontype/NoTypeInfo1654Test.java | 10 ++-- 3 files changed, 33 insertions(+), 32 deletions(-) diff --git a/src/main/java/tools/jackson/databind/jsontype/TypeResolverProvider.java b/src/main/java/tools/jackson/databind/jsontype/TypeResolverProvider.java index cad5f6a727..ba906ca261 100644 --- a/src/main/java/tools/jackson/databind/jsontype/TypeResolverProvider.java +++ b/src/main/java/tools/jackson/databind/jsontype/TypeResolverProvider.java @@ -197,7 +197,7 @@ public TypeDeserializer findPropertyContentTypeDeserializer(DeserializationConte } // [databind#1654]: Explicit `@JsonTypeInfo(Id.NONE)` should block class-level type info if (b == NO_RESOLVER) { - return new NoOpTypeDeserializer(contentType, null); + return NoOpTypeDeserializer.forBaseType(ctxt, contentType); } Collection subtypes = config.getSubtypeResolver().collectAndResolveSubtypesByTypeId(config, accessor, contentType); diff --git a/src/main/java/tools/jackson/databind/jsontype/impl/NoOpTypeDeserializer.java b/src/main/java/tools/jackson/databind/jsontype/impl/NoOpTypeDeserializer.java index 9308fe493b..0c259b3c85 100644 --- a/src/main/java/tools/jackson/databind/jsontype/impl/NoOpTypeDeserializer.java +++ b/src/main/java/tools/jackson/databind/jsontype/impl/NoOpTypeDeserializer.java @@ -6,6 +6,7 @@ import tools.jackson.databind.*; import tools.jackson.databind.jsontype.TypeDeserializer; import tools.jackson.databind.jsontype.TypeIdResolver; +import tools.jackson.databind.util.ClassUtil; /** * Special {@link TypeDeserializer} implementation used to explicitly @@ -22,23 +23,27 @@ public class NoOpTypeDeserializer extends TypeDeserializer { private final JavaType _baseType; - private final ValueDeserializer _deserializer; + private final BeanProperty _property; - public NoOpTypeDeserializer(JavaType baseType, ValueDeserializer deser) { + // Dynamically constructed deserializer + private volatile ValueDeserializer _deserializer; + + private NoOpTypeDeserializer(JavaType baseType, BeanProperty prop) { _baseType = baseType; - _deserializer = deser; + _property = prop; } - public NoOpTypeDeserializer withDeserializer(ValueDeserializer deser) { - if (_deserializer == deser) { - return this; - } - return new NoOpTypeDeserializer(_baseType, deser); + public static NoOpTypeDeserializer forBaseType(DeserializationContext ctxt, + JavaType baseType) { + return new NoOpTypeDeserializer(baseType, null); } @Override public TypeDeserializer forProperty(BeanProperty prop) { - return this; + if (_property == prop) { + return this; + } + return new NoOpTypeDeserializer(_baseType, prop); } @Override @@ -63,34 +68,30 @@ public Class getDefaultImpl() { } @Override - public Object deserializeTypedFromObject(JsonParser p, - DeserializationContext ctxt) throws JacksonException + public Object deserializeTypedFromObject(JsonParser p, DeserializationContext ctxt) + throws JacksonException { - // Just deserialize without type info return _deserialize(p, ctxt); } @Override - public Object deserializeTypedFromArray(JsonParser p, - DeserializationContext ctxt) throws JacksonException + public Object deserializeTypedFromArray(JsonParser p, DeserializationContext ctxt) + throws JacksonException { - // Just deserialize without type info return _deserialize(p, ctxt); } @Override - public Object deserializeTypedFromScalar(JsonParser p, - DeserializationContext ctxt) throws JacksonException + public Object deserializeTypedFromScalar(JsonParser p, DeserializationContext ctxt) + throws JacksonException { - // Just deserialize without type info return _deserialize(p, ctxt); } @Override - public Object deserializeTypedFromAny(JsonParser p, - DeserializationContext ctxt) throws JacksonException + public Object deserializeTypedFromAny(JsonParser p, DeserializationContext ctxt) + throws JacksonException { - // Just deserialize without type info return _deserialize(p, ctxt); } @@ -99,14 +100,16 @@ protected Object _deserialize(JsonParser p, DeserializationContext ctxt) { ValueDeserializer deser = _deserializer; - // Find deserializer for the base type (this will find custom deserializers - // registered for this type, including those from @JsonDeserialize annotations) + // Find deserializer for the base type, given property (if any). + // This will find custom deserializers registered for this type, + // including those from @JsonDeserialize annotations) if (deser == null) { - deser = ctxt.findContextualValueDeserializer(_baseType, null); + deser = ctxt.findContextualValueDeserializer(_baseType, _property); if (deser == null) { ctxt.reportBadDefinition(_baseType, - "Cannot find deserializer for type " + _baseType); + "Cannot find deserializer for type " +ClassUtil.getTypeDescription(_baseType)); } + _deserializer = deser; } return deser.deserialize(p, ctxt); } diff --git a/src/test/java/tools/jackson/databind/jsontype/NoTypeInfo1654Test.java b/src/test/java/tools/jackson/databind/jsontype/NoTypeInfo1654Test.java index 82e046ff38..9ed054e6ba 100644 --- a/src/test/java/tools/jackson/databind/jsontype/NoTypeInfo1654Test.java +++ b/src/test/java/tools/jackson/databind/jsontype/NoTypeInfo1654Test.java @@ -13,14 +13,13 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -class NoTypeInfo1654Test extends DatabindTestUtil { - +class NoTypeInfo1654Test extends DatabindTestUtil +{ @JsonTypeInfo(use = JsonTypeInfo.Id.NAME) static class Value1654 { public int x; - protected Value1654() { - } + protected Value1654() { } public Value1654(int x) { this.x = x; @@ -30,8 +29,7 @@ public Value1654(int x) { static class Value1654TypedContainer { public List values; - protected Value1654TypedContainer() { - } + protected Value1654TypedContainer() { } public Value1654TypedContainer(Value1654... v) { values = Arrays.asList(v); From 522144c6bc8978aaa14ffc254e0467467ed8ab26 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sat, 6 Dec 2025 10:28:38 -0800 Subject: [PATCH 04/19] ... --- .../java/tools/jackson/databind/jsontype/NoTypeInfo1654Test.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/java/tools/jackson/databind/jsontype/NoTypeInfo1654Test.java b/src/test/java/tools/jackson/databind/jsontype/NoTypeInfo1654Test.java index 9ed054e6ba..ae25dedf64 100644 --- a/src/test/java/tools/jackson/databind/jsontype/NoTypeInfo1654Test.java +++ b/src/test/java/tools/jackson/databind/jsontype/NoTypeInfo1654Test.java @@ -52,6 +52,7 @@ public Value1654UntypedContainer(Value1654... v) { static class Value1654Deserializer extends ValueDeserializer { @Override public Value1654 deserialize(JsonParser p, DeserializationContext ctxt) { + //JsonNode n = ctxt.readTree(p); p.skipChildren(); return new Value1654(13); } From bb7b02403b50d43e770fa6f0a3fde8380291455b Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sat, 6 Dec 2025 15:43:53 -0800 Subject: [PATCH 05/19] Trim down #1654 test case --- .../databind/jsontype/NoTypeInfo1654Test.java | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/test/java/tools/jackson/databind/jsontype/NoTypeInfo1654Test.java b/src/test/java/tools/jackson/databind/jsontype/NoTypeInfo1654Test.java index ae25dedf64..dfabf39b31 100644 --- a/src/test/java/tools/jackson/databind/jsontype/NoTypeInfo1654Test.java +++ b/src/test/java/tools/jackson/databind/jsontype/NoTypeInfo1654Test.java @@ -37,7 +37,7 @@ public Value1654TypedContainer(Value1654... v) { } static class Value1654UntypedContainer { - @JsonDeserialize(contentUsing = Value1654Deserializer.class) + //@JsonDeserialize(contentUsing = Value1654Deserializer.class) @JsonTypeInfo(use = JsonTypeInfo.Id.NONE) public List values; @@ -49,6 +49,7 @@ public Value1654UntypedContainer(Value1654... v) { } } + /* static class Value1654Deserializer extends ValueDeserializer { @Override public Value1654 deserialize(JsonParser p, DeserializationContext ctxt) { @@ -57,26 +58,30 @@ public Value1654 deserialize(JsonParser p, DeserializationContext ctxt) { return new Value1654(13); } } + */ private final ObjectMapper MAPPER = newJsonMapper(); - // [databind#1654] + // [databind#1654]: no override, default polymorphic type id @Test - void noTypeElementOverride() throws Exception { - // egular typed case + void withoutNoTypeElementOverrideSerAndDeser() throws Exception { + // regular typed case String json = MAPPER.writeValueAsString(new Value1654TypedContainer( new Value1654(1), - new Value1654(2), - new Value1654(3) + new Value1654(2) )); + String typeId = Value1654.class.getName(); + typeId = "'@type':'" + typeId.substring(typeId.lastIndexOf('.') + 1) + "'"; + assertEquals(a2q("{'values':[{"+typeId+",'x':1},{"+typeId+",'x':2}]}"), json); + Value1654TypedContainer result = MAPPER.readValue(json, Value1654TypedContainer.class); - assertEquals(3, result.values.size()); + assertEquals(2, result.values.size()); assertEquals(2, result.values.get(1).x); } - // [databind#1654] + // [databind#1654]: override, no polymorphic type id @Test - void noTypeInfoOverrideSer() throws Exception { + void withNoTypeInfoOverrideSer() throws Exception { Value1654UntypedContainer cont = new Value1654UntypedContainer( new Value1654(3), new Value1654(7) @@ -87,7 +92,7 @@ void noTypeInfoOverrideSer() throws Exception { // [databind#1654] @Test - void noTypeInfoOverrideDeser() throws Exception { + void withNoTypeInfoOverrideDeser() throws Exception { // and then actual failing case final String noTypeJson = a2q( "{'values':[{'x':3},{'x':7}]}" From be36a0698213f8e798631a7f4095840b07cd242c Mon Sep 17 00:00:00 2001 From: Jackson Date: Sun, 7 Dec 2025 19:39:43 +0900 Subject: [PATCH 06/19] Fix #1654: @JsonDeserialize(contentUsing) ignored when @JsonTypeInfo(use=NONE) --- .../jackson/databind/deser/BasicDeserializerFactory.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/java/tools/jackson/databind/deser/BasicDeserializerFactory.java b/src/main/java/tools/jackson/databind/deser/BasicDeserializerFactory.java index e7585cd05f..36c559b08f 100644 --- a/src/main/java/tools/jackson/databind/deser/BasicDeserializerFactory.java +++ b/src/main/java/tools/jackson/databind/deser/BasicDeserializerFactory.java @@ -24,6 +24,7 @@ import tools.jackson.databind.ext.jdk8.OptionalLongDeserializer; import tools.jackson.databind.introspect.*; import tools.jackson.databind.jsontype.TypeDeserializer; +import tools.jackson.databind.jsontype.impl.NoOpTypeDeserializer; import tools.jackson.databind.type.*; import tools.jackson.databind.util.*; @@ -767,8 +768,12 @@ public ValueDeserializer createCollectionDeserializer(DeserializationContext // Then optional type info: if type has been resolved, we may already know type deserializer: TypeDeserializer contentTypeDeser = (TypeDeserializer) contentType.getTypeHandler(); - // but if not, may still be possible to find: - if (contentTypeDeser == null) { + // [databind#1654]: @JsonTypeInfo(use = Id.NONE) should not apply type deserializer + // when custom content deserializer is specified via @JsonDeserialize(contentUsing = ...) + if (contentTypeDeser instanceof NoOpTypeDeserializer) { + contentTypeDeser = null; + } else if (contentTypeDeser == null) { + // but if not, may still be possible to find: contentTypeDeser = ctxt.findTypeDeserializer(contentType); } // 23-Nov-2010, tatu: Custom deserializer? From 20dc7af7931965c57bf873d9ccf3e29db43cbf86 Mon Sep 17 00:00:00 2001 From: Jackson Date: Sun, 7 Dec 2025 21:05:11 +0900 Subject: [PATCH 07/19] Update test file for #1654 --- .../databind/jsontype/NoTypeInfo1654Test.java | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/src/test/java/tools/jackson/databind/jsontype/NoTypeInfo1654Test.java b/src/test/java/tools/jackson/databind/jsontype/NoTypeInfo1654Test.java index dfabf39b31..fde2d4a2ef 100644 --- a/src/test/java/tools/jackson/databind/jsontype/NoTypeInfo1654Test.java +++ b/src/test/java/tools/jackson/databind/jsontype/NoTypeInfo1654Test.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; +import tools.jackson.core.JacksonException; import tools.jackson.core.JsonParser; import tools.jackson.databind.*; import tools.jackson.databind.annotation.JsonDeserialize; @@ -49,7 +50,19 @@ public Value1654UntypedContainer(Value1654... v) { } } - /* + static class Value1654UsingDeserializerUntypedContainer { + @JsonDeserialize(contentUsing = Value1654Deserializer.class) + @JsonTypeInfo(use = JsonTypeInfo.Id.NONE) + public List values; + + protected Value1654UsingDeserializerUntypedContainer() { + } + + public Value1654UsingDeserializerUntypedContainer(Value1654... v) { + values = Arrays.asList(v); + } + } + static class Value1654Deserializer extends ValueDeserializer { @Override public Value1654 deserialize(JsonParser p, DeserializationContext ctxt) { @@ -58,7 +71,7 @@ public Value1654 deserialize(JsonParser p, DeserializationContext ctxt) { return new Value1654(13); } } - */ + private final ObjectMapper MAPPER = newJsonMapper(); @@ -92,7 +105,7 @@ void withNoTypeInfoOverrideSer() throws Exception { // [databind#1654] @Test - void withNoTypeInfoOverrideDeser() throws Exception { + void withNoTypeInfoDeser() throws Exception { // and then actual failing case final String noTypeJson = a2q( "{'values':[{'x':3},{'x':7}]}" @@ -101,4 +114,17 @@ void withNoTypeInfoOverrideDeser() throws Exception { assertEquals(2, unResult.values.size()); assertEquals(7, unResult.values.get(1).x); } + + // [databind#1654] + @Test + void withNoTypeInfoOverrideDeser() throws Exception { + // and then actual failing case + final String noTypeJson = a2q( + "{'values':[{'x':3},{'x':7}]}" + ); + Value1654UsingDeserializerUntypedContainer unResult = MAPPER.readValue(noTypeJson, Value1654UsingDeserializerUntypedContainer.class); + assertEquals(2, unResult.values.size()); + assertEquals(13, unResult.values.get(0).x); + assertEquals(13, unResult.values.get(1).x); + } } From 0c6b3c70330a3d14fa60ee400fec4490570c6b93 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sun, 7 Dec 2025 12:26:08 -0800 Subject: [PATCH 08/19] ... --- .../tools/jackson/databind/jsontype/NoTypeInfo1654Test.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/test/java/tools/jackson/databind/jsontype/NoTypeInfo1654Test.java b/src/test/java/tools/jackson/databind/jsontype/NoTypeInfo1654Test.java index fde2d4a2ef..825bab8cf2 100644 --- a/src/test/java/tools/jackson/databind/jsontype/NoTypeInfo1654Test.java +++ b/src/test/java/tools/jackson/databind/jsontype/NoTypeInfo1654Test.java @@ -38,7 +38,6 @@ public Value1654TypedContainer(Value1654... v) { } static class Value1654UntypedContainer { - //@JsonDeserialize(contentUsing = Value1654Deserializer.class) @JsonTypeInfo(use = JsonTypeInfo.Id.NONE) public List values; @@ -72,7 +71,6 @@ public Value1654 deserialize(JsonParser p, DeserializationContext ctxt) { } } - private final ObjectMapper MAPPER = newJsonMapper(); // [databind#1654]: no override, default polymorphic type id From 27cf9f903947d3de02531e8ed5ac3cd385fea56d Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sun, 7 Dec 2025 12:47:42 -0800 Subject: [PATCH 09/19] Minor tweaking of test case --- .../databind/jsontype/NoTypeInfo1654Test.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/test/java/tools/jackson/databind/jsontype/NoTypeInfo1654Test.java b/src/test/java/tools/jackson/databind/jsontype/NoTypeInfo1654Test.java index 825bab8cf2..bf515fd4c3 100644 --- a/src/test/java/tools/jackson/databind/jsontype/NoTypeInfo1654Test.java +++ b/src/test/java/tools/jackson/databind/jsontype/NoTypeInfo1654Test.java @@ -6,8 +6,8 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; -import tools.jackson.core.JacksonException; import tools.jackson.core.JsonParser; + import tools.jackson.databind.*; import tools.jackson.databind.annotation.JsonDeserialize; import tools.jackson.databind.testutil.DatabindTestUtil; @@ -41,8 +41,7 @@ static class Value1654UntypedContainer { @JsonTypeInfo(use = JsonTypeInfo.Id.NONE) public List values; - protected Value1654UntypedContainer() { - } + protected Value1654UntypedContainer() { } public Value1654UntypedContainer(Value1654... v) { values = Arrays.asList(v); @@ -54,8 +53,7 @@ static class Value1654UsingDeserializerUntypedContainer { @JsonTypeInfo(use = JsonTypeInfo.Id.NONE) public List values; - protected Value1654UsingDeserializerUntypedContainer() { - } + protected Value1654UsingDeserializerUntypedContainer() { } public Value1654UsingDeserializerUntypedContainer(Value1654... v) { values = Arrays.asList(v); @@ -65,9 +63,11 @@ public Value1654UsingDeserializerUntypedContainer(Value1654... v) { static class Value1654Deserializer extends ValueDeserializer { @Override public Value1654 deserialize(JsonParser p, DeserializationContext ctxt) { - //JsonNode n = ctxt.readTree(p); - p.skipChildren(); - return new Value1654(13); + JsonNode n = ctxt.readTree(p); + if (!n.has("v")) { + ctxt.reportInputMismatch(Value1654.class, "Bad JSON input (no 'v'): " + n); + } + return new Value1654(n.path("v").intValue()); } } @@ -118,11 +118,11 @@ void withNoTypeInfoDeser() throws Exception { void withNoTypeInfoOverrideDeser() throws Exception { // and then actual failing case final String noTypeJson = a2q( - "{'values':[{'x':3},{'x':7}]}" + "{'values':[{'v':3},{'v':7}]}" ); Value1654UsingDeserializerUntypedContainer unResult = MAPPER.readValue(noTypeJson, Value1654UsingDeserializerUntypedContainer.class); assertEquals(2, unResult.values.size()); - assertEquals(13, unResult.values.get(0).x); - assertEquals(13, unResult.values.get(1).x); + assertEquals(3, unResult.values.get(0).x); + assertEquals(7, unResult.values.get(1).x); } } From ca93f0bce28e408bc129b21dfd0681cd76215775 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sun, 7 Dec 2025 14:02:22 -0800 Subject: [PATCH 10/19] Add last test case (failing for now) with no-type-info, custom serializer --- .../databind/jsontype/NoTypeInfo1654Test.java | 50 +++++++++++++++---- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/src/test/java/tools/jackson/databind/jsontype/NoTypeInfo1654Test.java b/src/test/java/tools/jackson/databind/jsontype/NoTypeInfo1654Test.java index bf515fd4c3..184bfef858 100644 --- a/src/test/java/tools/jackson/databind/jsontype/NoTypeInfo1654Test.java +++ b/src/test/java/tools/jackson/databind/jsontype/NoTypeInfo1654Test.java @@ -6,10 +6,13 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; +import tools.jackson.core.JacksonException; +import tools.jackson.core.JsonGenerator; import tools.jackson.core.JsonParser; import tools.jackson.databind.*; import tools.jackson.databind.annotation.JsonDeserialize; +import tools.jackson.databind.annotation.JsonSerialize; import tools.jackson.databind.testutil.DatabindTestUtil; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -48,14 +51,15 @@ public Value1654UntypedContainer(Value1654... v) { } } - static class Value1654UsingDeserializerUntypedContainer { + static class Value1654UsingCustomSerDeserUntypedContainer { @JsonDeserialize(contentUsing = Value1654Deserializer.class) + @JsonSerialize(contentUsing = Value1654Serializer.class) @JsonTypeInfo(use = JsonTypeInfo.Id.NONE) public List values; - protected Value1654UsingDeserializerUntypedContainer() { } + protected Value1654UsingCustomSerDeserUntypedContainer() { } - public Value1654UsingDeserializerUntypedContainer(Value1654... v) { + public Value1654UsingCustomSerDeserUntypedContainer(Value1654... v) { values = Arrays.asList(v); } } @@ -71,6 +75,17 @@ public Value1654 deserialize(JsonParser p, DeserializationContext ctxt) { } } + + static class Value1654Serializer extends ValueSerializer { + @Override + public void serialize(Value1654 value, JsonGenerator gen, SerializationContext ctxt) + throws JacksonException { + gen.writeStartObject(value); + gen.writeNumberProperty("v", value.x); + gen.writeEndObject(); + } + } + private final ObjectMapper MAPPER = newJsonMapper(); // [databind#1654]: no override, default polymorphic type id @@ -90,9 +105,9 @@ void withoutNoTypeElementOverrideSerAndDeser() throws Exception { assertEquals(2, result.values.get(1).x); } - // [databind#1654]: override, no polymorphic type id + // [databind#1654]: override, no polymorphic type id, serialization @Test - void withNoTypeInfoOverrideSer() throws Exception { + void withNoTypeInfoDefaultSer() throws Exception { Value1654UntypedContainer cont = new Value1654UntypedContainer( new Value1654(3), new Value1654(7) @@ -101,28 +116,41 @@ void withNoTypeInfoOverrideSer() throws Exception { MAPPER.writeValueAsString(cont)); } - // [databind#1654] + // [databind#1654]: override, no polymorphic type id, deserialization @Test - void withNoTypeInfoDeser() throws Exception { - // and then actual failing case + void withNoTypeInfoDefaultDeser() throws Exception { final String noTypeJson = a2q( "{'values':[{'x':3},{'x':7}]}" ); - Value1654UntypedContainer unResult = MAPPER.readValue(noTypeJson, Value1654UntypedContainer.class); + Value1654UntypedContainer unResult = MAPPER.readValue(noTypeJson, + Value1654UntypedContainer.class); assertEquals(2, unResult.values.size()); assertEquals(7, unResult.values.get(1).x); } - // [databind#1654] + // [databind#1654]: override, no polymorphic type id, custom serialization + @Test + void withNoTypeInfoOverrideSer() throws Exception { + Value1654UntypedContainer cont = new Value1654UntypedContainer( + new Value1654(1), + new Value1654(2) + ); + assertEquals(a2q("{'values':[{'v':1},{'v':2}]}"), + MAPPER.writeValueAsString(cont)); + } + + // [databind#1654]: override, no polymorphic type id, custom deserialization @Test void withNoTypeInfoOverrideDeser() throws Exception { // and then actual failing case final String noTypeJson = a2q( "{'values':[{'v':3},{'v':7}]}" ); - Value1654UsingDeserializerUntypedContainer unResult = MAPPER.readValue(noTypeJson, Value1654UsingDeserializerUntypedContainer.class); + Value1654UsingCustomSerDeserUntypedContainer unResult = MAPPER.readValue(noTypeJson, + Value1654UsingCustomSerDeserUntypedContainer.class); assertEquals(2, unResult.values.size()); assertEquals(3, unResult.values.get(0).x); assertEquals(7, unResult.values.get(1).x); } + } From c39c7a489538416c93e319f85f9478c80aadca22 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sun, 7 Dec 2025 16:52:20 -0800 Subject: [PATCH 11/19] Fix test "withNoTypeInfoOverrideSer()" --- .../tools/jackson/databind/jsontype/NoTypeInfo1654Test.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/tools/jackson/databind/jsontype/NoTypeInfo1654Test.java b/src/test/java/tools/jackson/databind/jsontype/NoTypeInfo1654Test.java index 184bfef858..523cd2d8c8 100644 --- a/src/test/java/tools/jackson/databind/jsontype/NoTypeInfo1654Test.java +++ b/src/test/java/tools/jackson/databind/jsontype/NoTypeInfo1654Test.java @@ -131,7 +131,7 @@ void withNoTypeInfoDefaultDeser() throws Exception { // [databind#1654]: override, no polymorphic type id, custom serialization @Test void withNoTypeInfoOverrideSer() throws Exception { - Value1654UntypedContainer cont = new Value1654UntypedContainer( + Value1654UsingCustomSerDeserUntypedContainer cont = new Value1654UsingCustomSerDeserUntypedContainer( new Value1654(1), new Value1654(2) ); From aa09d02fd8c442f02da0c8eb1b6b1b691acfb482 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sun, 7 Dec 2025 17:20:52 -0800 Subject: [PATCH 12/19] Update release notes wrt #1654 --- release-notes/VERSION | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes/VERSION b/release-notes/VERSION index 44c020520c..e4b7bf5dd8 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -14,6 +14,7 @@ Versions: 3.x (for earlier see VERSION-2.x) #1654: @JsonDeserialize(contentUsing=...) is ignored if content type is determined by @JsonTypeInfo (reported by @pdegoeje) + (fix by @cowtowncoder, @JacksonJang) #1980: Add method `remove(JsonPointer)` in `ContainerNode` (fix by @cowtowncoder, w/ Claude code) #3964: Deserialization issue: MismatchedInputException, Bean not From afb0049d28872fd51296332baccd6587ae993bb6 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sun, 7 Dec 2025 17:43:36 -0800 Subject: [PATCH 13/19] Update to use JsonTypeInfo.As.NOTHING --- .../jackson/databind/jsontype/impl/NoOpTypeDeserializer.java | 3 +-- .../jackson/databind/jsontype/impl/NoOpTypeSerializer.java | 5 +++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/tools/jackson/databind/jsontype/impl/NoOpTypeDeserializer.java b/src/main/java/tools/jackson/databind/jsontype/impl/NoOpTypeDeserializer.java index 0c259b3c85..bed72ec4d1 100644 --- a/src/main/java/tools/jackson/databind/jsontype/impl/NoOpTypeDeserializer.java +++ b/src/main/java/tools/jackson/databind/jsontype/impl/NoOpTypeDeserializer.java @@ -48,8 +48,7 @@ public TypeDeserializer forProperty(BeanProperty prop) { @Override public JsonTypeInfo.As getTypeInclusion() { - // No proper value but need to return something - return JsonTypeInfo.As.EXISTING_PROPERTY; + return JsonTypeInfo.As.NOTHING; } @Override diff --git a/src/main/java/tools/jackson/databind/jsontype/impl/NoOpTypeSerializer.java b/src/main/java/tools/jackson/databind/jsontype/impl/NoOpTypeSerializer.java index 0820ea70ea..cf106ef59c 100644 --- a/src/main/java/tools/jackson/databind/jsontype/impl/NoOpTypeSerializer.java +++ b/src/main/java/tools/jackson/databind/jsontype/impl/NoOpTypeSerializer.java @@ -38,8 +38,7 @@ public TypeSerializer forProperty(SerializationContext ctxt, BeanProperty prop) @Override public JsonTypeInfo.As getTypeInclusion() { - // No proper one to use but must return something: - return JsonTypeInfo.As.EXISTING_PROPERTY; + return JsonTypeInfo.As.NOTHING; } @Override @@ -49,6 +48,8 @@ public String getPropertyName() { @Override public TypeIdResolver getTypeIdResolver() { + // 07-Dec-2025, tatu: [databind#1654] Important! Indicates + // that no actual Type Id handled. return null; } From e5f926fc55a894de78d23c2974aa756f2a7a8263 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sun, 7 Dec 2025 17:45:53 -0800 Subject: [PATCH 14/19] Remove now unnecessary comment --- .../jackson/databind/jsontype/impl/NoOpTypeSerializer.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/tools/jackson/databind/jsontype/impl/NoOpTypeSerializer.java b/src/main/java/tools/jackson/databind/jsontype/impl/NoOpTypeSerializer.java index cf106ef59c..371091177a 100644 --- a/src/main/java/tools/jackson/databind/jsontype/impl/NoOpTypeSerializer.java +++ b/src/main/java/tools/jackson/databind/jsontype/impl/NoOpTypeSerializer.java @@ -48,8 +48,6 @@ public String getPropertyName() { @Override public TypeIdResolver getTypeIdResolver() { - // 07-Dec-2025, tatu: [databind#1654] Important! Indicates - // that no actual Type Id handled. return null; } From a19c944249fdf9f0cb2c146045d43af6db95f992 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8C=E1=85=A1=E1=86=BC=E1=84=92=E1=85=AD=E1=84=8B?= =?UTF-8?q?=E1=85=AF=E1=86=AB?= Date: Mon, 8 Dec 2025 13:35:25 +0900 Subject: [PATCH 15/19] Fix #1654: Add handling for NoOpTypeSerializer in CollectionSerializer --- .../tools/jackson/databind/ser/jdk/CollectionSerializer.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/tools/jackson/databind/ser/jdk/CollectionSerializer.java b/src/main/java/tools/jackson/databind/ser/jdk/CollectionSerializer.java index 065332dead..eda416ee4a 100644 --- a/src/main/java/tools/jackson/databind/ser/jdk/CollectionSerializer.java +++ b/src/main/java/tools/jackson/databind/ser/jdk/CollectionSerializer.java @@ -11,6 +11,7 @@ import tools.jackson.databind.SerializationContext; import tools.jackson.databind.ValueSerializer; import tools.jackson.databind.jsontype.TypeSerializer; +import tools.jackson.databind.jsontype.impl.NoOpTypeSerializer; import tools.jackson.databind.ser.impl.PropertySerializerMap; import tools.jackson.databind.ser.std.AsArraySerializerBase; import tools.jackson.databind.ser.std.StdContainerSerializer; @@ -171,6 +172,8 @@ public void serializeContentsUsing(Collection value, JsonGenerator g, Seriali } else { if (typeSer == null) { ser.serialize(elem, g, provider); + } else if (typeSer instanceof NoOpTypeSerializer) { + ser.serialize(elem, g, provider); } else { ser.serializeWithType(elem, g, provider, typeSer); } From 66f6ffb24ca850e0acf1bbf1d36bb68d0dd8e002 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sun, 7 Dec 2025 21:03:47 -0800 Subject: [PATCH 16/19] Improve test with additional coverage --- .../databind/jsontype/NoTypeInfo1654Test.java | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/test/java/tools/jackson/databind/jsontype/NoTypeInfo1654Test.java b/src/test/java/tools/jackson/databind/jsontype/NoTypeInfo1654Test.java index 523cd2d8c8..280cc0d099 100644 --- a/src/test/java/tools/jackson/databind/jsontype/NoTypeInfo1654Test.java +++ b/src/test/java/tools/jackson/databind/jsontype/NoTypeInfo1654Test.java @@ -64,6 +64,19 @@ public Value1654UsingCustomSerDeserUntypedContainer(Value1654... v) { } } + static class SingleValue1654UsingCustomSerDeserUntyped { + @JsonDeserialize(using = Value1654Deserializer.class) + @JsonSerialize(using = Value1654Serializer.class) + @JsonTypeInfo(use = JsonTypeInfo.Id.NONE) + public Value1654 value; + + protected SingleValue1654UsingCustomSerDeserUntyped() { } + + public SingleValue1654UsingCustomSerDeserUntyped(Value1654 v) { + value = v; + } + } + static class Value1654Deserializer extends ValueDeserializer { @Override public Value1654 deserialize(JsonParser p, DeserializationContext ctxt) { @@ -142,7 +155,6 @@ void withNoTypeInfoOverrideSer() throws Exception { // [databind#1654]: override, no polymorphic type id, custom deserialization @Test void withNoTypeInfoOverrideDeser() throws Exception { - // and then actual failing case final String noTypeJson = a2q( "{'values':[{'v':3},{'v':7}]}" ); @@ -153,4 +165,23 @@ void withNoTypeInfoOverrideDeser() throws Exception { assertEquals(7, unResult.values.get(1).x); } + // // And then validation for individual value, not in Container + + // override, no polymorphic type id, custom serialization + @Test + void singleWithNoTypeInfoOverrideSer() throws Exception { + SingleValue1654UsingCustomSerDeserUntyped wrapper = new SingleValue1654UsingCustomSerDeserUntyped( + new Value1654(42)); + assertEquals(a2q("{'value':{'v':42}}"), + MAPPER.writeValueAsString(wrapper)); + } + + // override, no polymorphic type id, custom deserialization + @Test + void singleWithNoTypeInfoOverrideDeser() throws Exception { + String noTypeJson = a2q("{'value':{'v':42}}"); + SingleValue1654UsingCustomSerDeserUntyped result = MAPPER.readValue(noTypeJson, + SingleValue1654UsingCustomSerDeserUntyped.class); + assertEquals(42,result.value.x); + } } From a6d3a52b7b3330e0a5ea4688bfa8f50b8066bb1b Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sun, 7 Dec 2025 21:04:21 -0800 Subject: [PATCH 17/19] Minor tweak needed for new inclusion value --- .../java/tools/jackson/databind/jsontype/TypeSerializer.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/tools/jackson/databind/jsontype/TypeSerializer.java b/src/main/java/tools/jackson/databind/jsontype/TypeSerializer.java index 7974b55962..c943133d5d 100644 --- a/src/main/java/tools/jackson/databind/jsontype/TypeSerializer.java +++ b/src/main/java/tools/jackson/databind/jsontype/TypeSerializer.java @@ -92,6 +92,10 @@ public WritableTypeId typeId(Object value, JsonToken valueShape) { case WRAPPER_OBJECT: typeIdDef.include = WritableTypeId.Inclusion.WRAPPER_OBJECT; break; + case NOTHING: + // 07-Dec-2025, tatu: No suitable constant to use. Should add "NOTHING"? + typeIdDef.include = null; + break; default: VersionUtil.throwInternal(); } From 271281d72c82f336e3868447d3f2404523963539 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Mon, 8 Dec 2025 17:18:29 -0800 Subject: [PATCH 18/19] Complete the fix, testing impovements --- src/main/java/tools/jackson/databind/ValueSerializer.java | 8 ++++++++ .../jackson/databind/ser/jdk/CollectionSerializer.java | 3 --- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/java/tools/jackson/databind/ValueSerializer.java b/src/main/java/tools/jackson/databind/ValueSerializer.java index 95aaf99a50..dbce05b901 100644 --- a/src/main/java/tools/jackson/databind/ValueSerializer.java +++ b/src/main/java/tools/jackson/databind/ValueSerializer.java @@ -4,6 +4,7 @@ import java.util.Set; import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonTypeInfo.As; import tools.jackson.core.*; import tools.jackson.databind.jsonFormatVisitors.JsonFormatVisitable; @@ -252,6 +253,13 @@ public void serializeWithType(T value, JsonGenerator gen, SerializationContext c TypeSerializer typeSer) throws JacksonException { + // 07-Dec-2025, tatu: [databind#1654] Check for "no-op" type serializer + // indirectly + if (typeSer.getTypeInclusion() == As.NOTHING) { + serialize(value, gen, ctxt); + return; + } + Class clz = handledType(); if (clz == null) { clz = value.getClass(); diff --git a/src/main/java/tools/jackson/databind/ser/jdk/CollectionSerializer.java b/src/main/java/tools/jackson/databind/ser/jdk/CollectionSerializer.java index eda416ee4a..065332dead 100644 --- a/src/main/java/tools/jackson/databind/ser/jdk/CollectionSerializer.java +++ b/src/main/java/tools/jackson/databind/ser/jdk/CollectionSerializer.java @@ -11,7 +11,6 @@ import tools.jackson.databind.SerializationContext; import tools.jackson.databind.ValueSerializer; import tools.jackson.databind.jsontype.TypeSerializer; -import tools.jackson.databind.jsontype.impl.NoOpTypeSerializer; import tools.jackson.databind.ser.impl.PropertySerializerMap; import tools.jackson.databind.ser.std.AsArraySerializerBase; import tools.jackson.databind.ser.std.StdContainerSerializer; @@ -172,8 +171,6 @@ public void serializeContentsUsing(Collection value, JsonGenerator g, Seriali } else { if (typeSer == null) { ser.serialize(elem, g, provider); - } else if (typeSer instanceof NoOpTypeSerializer) { - ser.serialize(elem, g, provider); } else { ser.serializeWithType(elem, g, provider, typeSer); } From 1470894051ec1ba011f8d24cdff7f0713552909a Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Mon, 8 Dec 2025 17:25:08 -0800 Subject: [PATCH 19/19] ... --- src/main/java/tools/jackson/databind/ValueSerializer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/tools/jackson/databind/ValueSerializer.java b/src/main/java/tools/jackson/databind/ValueSerializer.java index dbce05b901..d138228af7 100644 --- a/src/main/java/tools/jackson/databind/ValueSerializer.java +++ b/src/main/java/tools/jackson/databind/ValueSerializer.java @@ -265,8 +265,8 @@ public void serializeWithType(T value, JsonGenerator gen, SerializationContext c clz = value.getClass(); } ctxt.reportBadDefinition(clz, String.format( - "Type id handling not implemented for type %s (by serializer of type %s)", - clz.getName(), getClass().getName())); +"Type id handling (method `serializeWithType()`) not implemented for type %s (by serializer of type %s)", + ClassUtil.nameOf(clz), ClassUtil.nameOf(getClass()))); } /*