Skip to content

Commit 2fea0d2

Browse files
committed
Merge branch '2.16'
2 parents 62399dc + 2cea7c9 commit 2fea0d2

File tree

4 files changed

+138
-7
lines changed

4 files changed

+138
-7
lines changed

release-notes/CREDITS-2.x

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1505,7 +1505,7 @@ PJ Fanning (pjfanning@github)
15051505
(2.14.0)
15061506
* Contributed #3837: Set transformer factory attributes to improve protection against XXE
15071507
(2.14.3)
1508-
* ... and countless more
1508+
- And NUMEROUS other contributions not listed here! (for 2.15 and above)
15091509
15101510
Igor Shymko (ancane@github)
15111511
* Contributed #3500: Add optional explicit `JsonSubTypes` repeated names check
@@ -1578,7 +1578,7 @@ Joo Hyuk Kim (JooHyukKim@github)
15781578
* Contributed #3819: Add convenience method `SimpleBeanPropertyFilter.filterOutAll()` as
15791579
counterpart of `serializeAll()`
15801580
(2.15.0)
1581-
* And NUMEROUS other contributions not listed here! (for 2.16 and above)
1581+
- And NUMEROUS other contributions not listed here! (for 2.16 and above)
15821582
15831583
Vojtěch Knyttl (knyttl@github)
15841584
* Requested #3053: Allow serializing enums to lowercase (`EnumFeature.WRITE_ENUMS_TO_LOWERCASE`)

release-notes/VERSION-2.x

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ Project: jackson-databind
66

77
2.16.0 (not yet released)
88

9+
#3877: Add new `OptBoolean` valued property in `@JsonTypeInfo`, handling,
10+
to allow per-polymorphic type loose Type Id handling
11+
(contributed by Joo-Hyuk K)
912
#3924: Incorrect target type when disabling coercion, trying to deserialize
1013
String from Array/Object
1114
(reported by João G)

src/main/java/tools/jackson/databind/jsontype/impl/StdTypeResolverBuilder.java

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@ public class StdTypeResolverBuilder
3030
*/
3131
protected boolean _typeIdVisible = false;
3232

33+
/**
34+
*
35+
* Boolean value configured through {@link JsonTypeInfo#requireTypeIdForSubtypes}.
36+
* If this value is not {@code null}, this value should override the global configuration of
37+
* {@link tools.jackson.databind.MapperFeature#REQUIRE_TYPE_ID_FOR_SUBTYPES}.
38+
*/
39+
protected Boolean _requireTypeIdForSubtypes;
40+
3341
/**
3442
* Default class to use in case type information is not available
3543
* or is broken.
@@ -80,8 +88,6 @@ protected static String _propName(String propName, JsonTypeInfo.Id idType) {
8088

8189
/**
8290
* Copy-constructor
83-
*
84-
* @since 2.13
8591
*/
8692
protected StdTypeResolverBuilder(StdTypeResolverBuilder base,
8793
Class<?> defaultImpl)
@@ -379,7 +385,10 @@ protected boolean allowPrimitiveTypes(DatabindContext ctxt,
379385

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

0 commit comments

Comments
 (0)