Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
4349557
Fix #1654
cowtowncoder Dec 6, 2025
57d8d92
Minor clean up
cowtowncoder Dec 6, 2025
5845399
Bigger PR rewrite
cowtowncoder Dec 6, 2025
522144c
...
cowtowncoder Dec 6, 2025
e74a36b
Merge branch '3.x' into tatu-claude/3.1/1654-no-type-info
cowtowncoder Dec 6, 2025
cc93b8f
Merge branch '3.x' into tatu-claude/3.1/1654-no-type-info
cowtowncoder Dec 6, 2025
bb7b024
Trim down #1654 test case
cowtowncoder Dec 6, 2025
be36a06
Fix #1654: @JsonDeserialize(contentUsing) ignored when @JsonTypeInfo(…
JacksonJang Dec 7, 2025
20dc7af
Update test file for #1654
JacksonJang Dec 7, 2025
99fa709
Merge branch '3.x' into 1654-fix-JsonDeserialize-ignore-when-JsonType…
cowtowncoder Dec 7, 2025
0c6b3c7
...
cowtowncoder Dec 7, 2025
27cf9f9
Minor tweaking of test case
cowtowncoder Dec 7, 2025
ca93f0b
Add last test case (failing for now) with no-type-info, custom serial…
cowtowncoder Dec 7, 2025
c39c7a4
Fix test "withNoTypeInfoOverrideSer()"
cowtowncoder Dec 8, 2025
aa09d02
Update release notes wrt #1654
cowtowncoder Dec 8, 2025
afb0049
Update to use JsonTypeInfo.As.NOTHING
cowtowncoder Dec 8, 2025
e5f926f
Remove now unnecessary comment
cowtowncoder Dec 8, 2025
a19c944
Fix #1654: Add handling for NoOpTypeSerializer in CollectionSerializer
JacksonJang Dec 8, 2025
66f6ffb
Improve test with additional coverage
cowtowncoder Dec 8, 2025
a6d3a52
Minor tweak needed for new inclusion value
cowtowncoder Dec 8, 2025
271281d
Complete the fix, testing impovements
cowtowncoder Dec 9, 2025
1470894
...
cowtowncoder Dec 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions release-notes/VERSION
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 10 additions & 2 deletions src/main/java/tools/jackson/databind/ValueSerializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -252,13 +253,20 @@ 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();
}
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())));
}

/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.*;

Expand Down Expand Up @@ -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?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ public TypeSerializer findPropertyTypeSerializer(SerializationContext ctxt,
if (b == NO_RESOLVER) {
// 07-Dec-2025, tatu: Should we actually do this? (No test coverage yet)
//return NoOpTypeSerializer.instance();

return null;
}
Collection<NamedType> subtypes = config.getSubtypeResolver().collectAndResolveSubtypesByClass(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
111 changes: 97 additions & 14 deletions src/test/java/tools/jackson/databind/jsontype/NoTypeInfo1654Test.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +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;
Expand Down Expand Up @@ -37,28 +41,63 @@ public Value1654TypedContainer(Value1654... v) {
}

static class Value1654UntypedContainer {
//@JsonDeserialize(contentUsing = Value1654Deserializer.class)
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
public List<Value1654> values;

protected Value1654UntypedContainer() {
}
protected Value1654UntypedContainer() { }

public Value1654UntypedContainer(Value1654... v) {
values = Arrays.asList(v);
}
}

/*
static class Value1654UsingCustomSerDeserUntypedContainer {
@JsonDeserialize(contentUsing = Value1654Deserializer.class)
@JsonSerialize(contentUsing = Value1654Serializer.class)
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
public List<Value1654> values;

protected Value1654UsingCustomSerDeserUntypedContainer() { }

public Value1654UsingCustomSerDeserUntypedContainer(Value1654... v) {
values = Arrays.asList(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<Value1654> {
@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());
}
}


static class Value1654Serializer extends ValueSerializer<Value1654> {
@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();

Expand All @@ -79,9 +118,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)
Expand All @@ -90,15 +129,59 @@ void withNoTypeInfoOverrideSer() throws Exception {
MAPPER.writeValueAsString(cont));
}

// [databind#1654]
// [databind#1654]: override, no polymorphic type id, deserialization
@Test
void withNoTypeInfoOverrideDeser() 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]: override, no polymorphic type id, custom serialization
@Test
void withNoTypeInfoOverrideSer() throws Exception {
Value1654UsingCustomSerDeserUntypedContainer cont = new Value1654UsingCustomSerDeserUntypedContainer(
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 {
final String noTypeJson = a2q(
"{'values':[{'v':3},{'v':7}]}"
);
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);
}

// // 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);
}
}