Skip to content

Commit c5eabf8

Browse files
authored
Add new @JsonTypeInfo.requireTypeIdForSubtypes usage (#3891)
Implements #3877
1 parent 58c2319 commit c5eabf8

File tree

2 files changed

+129
-1
lines changed

2 files changed

+129
-1
lines changed

src/main/java/com/fasterxml/jackson/databind/jsontype/impl/StdTypeResolverBuilder.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ public class StdTypeResolverBuilder
3737
protected boolean _typeIdVisible = false;
3838

3939
/**
40+
*
41+
* Boolean value configured through {@link JsonTypeInfo#requireTypeIdForSubtypes}.
42+
* If this value is not {@code null}, this value should override the global configuration of
43+
* {@link com.fasterxml.jackson.databind.MapperFeature#REQUIRE_TYPE_ID_FOR_SUBTYPES}.
44+
*
4045
* @since 2.16 (backported from Jackson 3.0)
4146
*/
4247
protected Boolean _requireTypeIdForSubtypes;
@@ -466,7 +471,10 @@ protected boolean allowPrimitiveTypes(MapperConfig<?> config,
466471

467472
/**
468473
* Determines whether strict type ID handling should be used for this type or not.
469-
* This will be enabled when either the type has type resolver annotations or if
474+
* This will be enabld as configured by {@link JsonTypeInfo#requireTypeIdForSubtypes()}
475+
* unless its value is {@link com.fasterxml.jackson.annotation.OptBoolean#DEFAULT}.
476+
* In case the value of {@link JsonTypeInfo#requireTypeIdForSubtypes()} is {@code OptBoolean.DEFAULT},
477+
* this will be enabled when either the type has type resolver annotations or if
470478
* {@link com.fasterxml.jackson.databind.MapperFeature#REQUIRE_TYPE_ID_FOR_SUBTYPES}
471479
* is enabled.
472480
*
@@ -479,6 +487,10 @@ protected boolean allowPrimitiveTypes(MapperConfig<?> config,
479487
* @since 2.15
480488
*/
481489
protected boolean _strictTypeIdHandling(DeserializationConfig config, JavaType baseType) {
490+
// [databind#3877]: per-type strict type handling, since 2.16
491+
if (_requireTypeIdForSubtypes != null && baseType.isConcrete()) {
492+
return _requireTypeIdForSubtypes;
493+
}
482494
if (config.isEnabled(MapperFeature.REQUIRE_TYPE_ID_FOR_SUBTYPES)) {
483495
return true;
484496
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package com.fasterxml.jackson.databind.jsontype;
2+
3+
import com.fasterxml.jackson.annotation.JsonSubTypes;
4+
import com.fasterxml.jackson.annotation.JsonTypeInfo;
5+
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
6+
import com.fasterxml.jackson.annotation.OptBoolean;
7+
import com.fasterxml.jackson.databind.BaseMapTest;
8+
import com.fasterxml.jackson.databind.MapperFeature;
9+
import com.fasterxml.jackson.databind.ObjectMapper;
10+
import com.fasterxml.jackson.databind.exc.InvalidTypeIdException;
11+
import com.fasterxml.jackson.databind.json.JsonMapper;
12+
13+
// [databind#3877]: allow configuration of per-type strict type handling
14+
public class OverrideStrictTypeInfoHandling3877Test extends BaseMapTest {
15+
16+
/*
17+
/**********************************************************
18+
/* Set Up
19+
/**********************************************************
20+
*/
21+
22+
@JsonTypeInfo(use = Id.NAME, requireTypeIdForSubtypes = OptBoolean.DEFAULT)
23+
@JsonSubTypes({
24+
@JsonSubTypes.Type(value = DoDefaultCommand.class, name = "do-default")})
25+
interface DefaultCommand {}
26+
27+
static class DoDefaultCommand implements DefaultCommand {}
28+
29+
@JsonTypeInfo(use = Id.NAME, requireTypeIdForSubtypes = OptBoolean.TRUE)
30+
@JsonSubTypes({
31+
@JsonSubTypes.Type(value = DoTrueCommand.class, name = "do-true")})
32+
interface TrueCommand {}
33+
34+
static class DoTrueCommand implements TrueCommand {}
35+
36+
@JsonTypeInfo(use = Id.NAME, requireTypeIdForSubtypes = OptBoolean.FALSE)
37+
@JsonSubTypes({
38+
@JsonSubTypes.Type(value = DoFalseCommand.class, name = "do-false")})
39+
interface FalseCommand {}
40+
41+
static class DoFalseCommand implements FalseCommand {}
42+
43+
/*
44+
/**********************************************************
45+
/* Tests
46+
/**********************************************************
47+
*/
48+
49+
private final ObjectMapper ENABLED_MAPPER = JsonMapper.builder().enable(MapperFeature.REQUIRE_TYPE_ID_FOR_SUBTYPES).build();
50+
private final ObjectMapper DISABLED_MAPPER = JsonMapper.builder().disable(MapperFeature.REQUIRE_TYPE_ID_FOR_SUBTYPES).build();
51+
private final ObjectMapper DEFAULT_MAPPER = JsonMapper.builder().build();
52+
53+
public void testMissingTypeId() throws Exception {
54+
// super types fail on missing-id no matter what
55+
verifyFailureMissingTypeId("{}", FalseCommand.class, ENABLED_MAPPER);
56+
verifyFailureMissingTypeId("{}", FalseCommand.class, DEFAULT_MAPPER);
57+
verifyFailureMissingTypeId("{}", FalseCommand.class, DISABLED_MAPPER);
58+
verifyFailureMissingTypeId("{}", TrueCommand.class, ENABLED_MAPPER);
59+
verifyFailureMissingTypeId("{}", TrueCommand.class, DEFAULT_MAPPER);
60+
verifyFailureMissingTypeId("{}", TrueCommand.class, DISABLED_MAPPER);
61+
verifyFailureMissingTypeId("{}", DefaultCommand.class, ENABLED_MAPPER);
62+
verifyFailureMissingTypeId("{}", DefaultCommand.class, DEFAULT_MAPPER);
63+
verifyFailureMissingTypeId("{}", DefaultCommand.class, DISABLED_MAPPER);
64+
65+
// overrides : to require type id
66+
verifySuccessWithNonNullAndType("{}", DoFalseCommand.class, ENABLED_MAPPER);
67+
verifySuccessWithNonNullAndType("{}", DoFalseCommand.class, DEFAULT_MAPPER);
68+
verifySuccessWithNonNullAndType("{}", DoFalseCommand.class, DISABLED_MAPPER);
69+
// overrides : do not require type id
70+
verifyFailureMissingTypeId("{}", DoTrueCommand.class, ENABLED_MAPPER);
71+
verifyFailureMissingTypeId("{}", DoTrueCommand.class, DEFAULT_MAPPER);
72+
verifyFailureMissingTypeId("{}", DoTrueCommand.class, DISABLED_MAPPER);
73+
// overrides : defaults
74+
verifyFailureMissingTypeId("{}", DoDefaultCommand.class, ENABLED_MAPPER);
75+
verifyFailureMissingTypeId("{}", DoDefaultCommand.class, DEFAULT_MAPPER);
76+
verifySuccessWithNonNullAndType("{}", DoDefaultCommand.class, DISABLED_MAPPER);
77+
}
78+
79+
public void testSuccessWhenTypeIdIsProvided() throws Exception {
80+
verifySuccessWithNonNullAndType(a2q("{'@type': 'do-false'}"), FalseCommand.class, ENABLED_MAPPER);
81+
verifySuccessWithNonNullAndType(a2q("{'@type': 'do-false'}"), FalseCommand.class, DEFAULT_MAPPER);
82+
verifySuccessWithNonNullAndType(a2q("{'@type': 'do-false'}"), FalseCommand.class, DISABLED_MAPPER);
83+
verifySuccessWithNonNullAndType(a2q("{'@type': 'do-false'}"), DoFalseCommand.class, ENABLED_MAPPER);
84+
verifySuccessWithNonNullAndType(a2q("{'@type': 'do-false'}"), DoFalseCommand.class, DEFAULT_MAPPER);
85+
verifySuccessWithNonNullAndType(a2q("{'@type': 'do-false'}"), DoFalseCommand.class, DISABLED_MAPPER);
86+
87+
verifySuccessWithNonNullAndType(a2q("{'@type': 'do-true'}"), TrueCommand.class, ENABLED_MAPPER);
88+
verifySuccessWithNonNullAndType(a2q("{'@type': 'do-true'}"), TrueCommand.class, DEFAULT_MAPPER);
89+
verifySuccessWithNonNullAndType(a2q("{'@type': 'do-true'}"), TrueCommand.class, DISABLED_MAPPER);
90+
verifySuccessWithNonNullAndType(a2q("{'@type': 'do-true'}"), DoTrueCommand.class, ENABLED_MAPPER);
91+
verifySuccessWithNonNullAndType(a2q("{'@type': 'do-true'}"), DoTrueCommand.class, DEFAULT_MAPPER);
92+
verifySuccessWithNonNullAndType(a2q("{'@type': 'do-true'}"), DoTrueCommand.class, DISABLED_MAPPER);
93+
94+
verifySuccessWithNonNullAndType(a2q("{'@type': 'do-default'}"), DefaultCommand.class, ENABLED_MAPPER);
95+
verifySuccessWithNonNullAndType(a2q("{'@type': 'do-default'}"), DefaultCommand.class, DEFAULT_MAPPER);
96+
verifySuccessWithNonNullAndType(a2q("{'@type': 'do-default'}"), DefaultCommand.class, DISABLED_MAPPER);
97+
verifySuccessWithNonNullAndType(a2q("{'@type': 'do-default'}"), DoDefaultCommand.class, ENABLED_MAPPER);
98+
verifySuccessWithNonNullAndType(a2q("{'@type': 'do-default'}"), DoDefaultCommand.class, DEFAULT_MAPPER);
99+
verifySuccessWithNonNullAndType(a2q("{'@type': 'do-default'}"), DoDefaultCommand.class, DISABLED_MAPPER);
100+
}
101+
102+
private <T> void verifySuccessWithNonNullAndType(String json, Class<T> clazz, ObjectMapper om) throws Exception {
103+
T bean = om.readValue(json, clazz);
104+
assertNotNull(bean);
105+
assertType(bean, clazz);
106+
}
107+
108+
private void verifyFailureMissingTypeId(String json, Class<?> clazz, ObjectMapper om) throws Exception {
109+
try {
110+
om.readValue(json, clazz);
111+
fail("Should not pass");
112+
} catch (InvalidTypeIdException e) {
113+
verifyException(e, "missing type id property '@type'");
114+
}
115+
}
116+
}

0 commit comments

Comments
 (0)