Skip to content

Commit cdc0e61

Browse files
authored
Merge branch '3.x' into 5405-map-format-shape-bug
2 parents 82d0af2 + d4fd7b2 commit cdc0e61

File tree

9 files changed

+904
-22
lines changed

9 files changed

+904
-22
lines changed

release-notes/VERSION

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ Versions: 3.x (for earlier see VERSION-2.x)
4646
#5456: Additional configuration (`JsonNodeFeature.STRIP_TRAILING_BIGDECIMAL_ZEROES`: true)
4747
to MapperBuilder#configureForJackson2 to closer match Jackson 2 behavior
4848
(contributed by @nrayburn-tech)
49+
#5475: Support `@JsonDeserializeAs` annotation
50+
(implemented by @cowtowncoder, w/ Claude code)
4951

5052
3.0.3 (28-Nov-2025)
5153

release-notes/VERSION-2.x

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ Project: jackson-databind
3030
(contributed by Hélios G)
3131
#5429: Formatting and Parsing of Large ISO-8601 Dates is inconsistent
3232
(reported by @DavTurns)
33+
#5475: Support `@JsonDeserializeAs` annotation
34+
(implemented by @cowtowncoder, w/ Claude code)
35+
#5476: Support `@JsonSerializeAs` annotation
36+
(implemented by @cowtowncoder, w/ Claude code)
3337

3438
2.20.2 (not yet released)
3539

src/main/java/tools/jackson/databind/introspect/JacksonAnnotationIntrospector.java

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ public class JacksonAnnotationIntrospector
3636
@SuppressWarnings("unchecked")
3737
private final static Class<? extends Annotation>[] ANNOTATIONS_TO_INFER_SER = (Class<? extends Annotation>[])
3838
new Class<?>[] {
39-
JsonSerialize.class,
39+
JsonSerialize.class, // databind-specific
40+
JsonSerializeAs.class, // since 2.21 alias (and eventual replacement) for `@JsonSerialize.as`
4041
JsonView.class,
4142
JsonFormat.class,
4243
JsonTypeInfo.class,
@@ -49,14 +50,15 @@ public class JacksonAnnotationIntrospector
4950
@SuppressWarnings("unchecked")
5051
private final static Class<? extends Annotation>[] ANNOTATIONS_TO_INFER_DESER = (Class<? extends Annotation>[])
5152
new Class<?>[] {
52-
JsonDeserialize.class,
53+
JsonDeserialize.class, // databind-specific
54+
JsonDeserializeAs.class, // since 2.21 alias (and eventual replacement) for `@JsonDeserialize.as`
5355
JsonView.class,
5456
JsonFormat.class,
5557
JsonTypeInfo.class,
5658
JsonUnwrapped.class,
5759
JsonBackReference.class,
5860
JsonManagedReference.class,
59-
JsonMerge.class // since 2.9
61+
JsonMerge.class
6062
};
6163

6264
// NOTE: To avoid mandatory Module dependency to "java.beans", support for 2
@@ -885,10 +887,15 @@ public JavaType refineSerializationType(final MapperConfig<?> config,
885887
final TypeFactory tf = config.getTypeFactory();
886888

887889
final JsonSerialize jsonSer = _findAnnotation(a, JsonSerialize.class);
890+
final JsonSerializeAs jsonSerAs = _findAnnotation(a, JsonSerializeAs.class);
888891

889892
// Ok: start by refining the main type itself; common to all types
890893

891-
final Class<?> serClass = (jsonSer == null) ? null : _classIfExplicit(jsonSer.as());
894+
Class<?> serClass = (jsonSer == null) ? null : _classIfExplicit(jsonSer.as());
895+
// 09-Dec-2025, tatu: [databind#5476] Also check @JsonSerializeAs
896+
if (serClass == null && jsonSerAs != null) {
897+
serClass = _classIfExplicit(jsonSerAs.value());
898+
}
892899
if (serClass != null) {
893900
if (type.hasRawClass(serClass)) {
894901
// 30-Nov-2015, tatu: As per [databind#1023], need to allow forcing of
@@ -923,7 +930,11 @@ public JavaType refineSerializationType(final MapperConfig<?> config,
923930
// First, key type (for Maps, Map-like types):
924931
if (type.isMapLikeType()) {
925932
JavaType keyType = type.getKeyType();
926-
final Class<?> keyClass = (jsonSer == null) ? null : _classIfExplicit(jsonSer.keyAs());
933+
Class<?> keyClass = (jsonSer == null) ? null : _classIfExplicit(jsonSer.keyAs());
934+
// 09-Dec-2025, tatu: [databind#5476] Also check @JsonSerializeAs
935+
if (keyClass == null && jsonSerAs != null) {
936+
keyClass = _classIfExplicit(jsonSerAs.key());
937+
}
927938
if (keyClass != null) {
928939
if (keyType.hasRawClass(keyClass)) {
929940
keyType = keyType.withStaticTyping();
@@ -958,7 +969,11 @@ public JavaType refineSerializationType(final MapperConfig<?> config,
958969
JavaType contentType = type.getContentType();
959970
if (contentType != null) { // collection[like], map[like], array, reference
960971
// And then value types for all containers:
961-
final Class<?> contentClass = (jsonSer == null) ? null : _classIfExplicit(jsonSer.contentAs());
972+
Class<?> contentClass = (jsonSer == null) ? null : _classIfExplicit(jsonSer.contentAs());
973+
// 09-Dec-2025, tatu: [databind#5476] Also check @JsonSerializeAs
974+
if (contentClass == null && jsonSerAs != null) {
975+
contentClass = _classIfExplicit(jsonSerAs.content());
976+
}
962977
if (contentClass != null) {
963978
if (contentType.hasRawClass(contentClass)) {
964979
contentType = contentType.withStaticTyping();
@@ -1245,9 +1260,14 @@ public JavaType refineDeserializationType(final MapperConfig<?> config,
12451260
final TypeFactory tf = config.getTypeFactory();
12461261

12471262
final JsonDeserialize jsonDeser = _findAnnotation(a, JsonDeserialize.class);
1263+
final JsonDeserializeAs jsonDeserAs = _findAnnotation(a, JsonDeserializeAs.class);
12481264

12491265
// Ok: start by refining the main type itself; common to all types
1250-
final Class<?> valueClass = (jsonDeser == null) ? null : _classIfExplicit(jsonDeser.as());
1266+
Class<?> valueClass = (jsonDeser == null) ? null : _classIfExplicit(jsonDeser.as());
1267+
// 09-Dec-2025, tatu: [databind#5475] Also check @JsonDeserializeAs
1268+
if (valueClass == null && jsonDeserAs != null) {
1269+
valueClass = _classIfExplicit(jsonDeserAs.value());
1270+
}
12511271
if ((valueClass != null) && !type.hasRawClass(valueClass)
12521272
&& !_primitiveAndWrapper(type, valueClass)) {
12531273
try {
@@ -1263,7 +1283,11 @@ public JavaType refineDeserializationType(final MapperConfig<?> config,
12631283
// First, key type (for Maps, Map-like types):
12641284
if (type.isMapLikeType()) {
12651285
JavaType keyType = type.getKeyType();
1266-
final Class<?> keyClass = (jsonDeser == null) ? null : _classIfExplicit(jsonDeser.keyAs());
1286+
Class<?> keyClass = (jsonDeser == null) ? null : _classIfExplicit(jsonDeser.keyAs());
1287+
// 09-Dec-2025, tatu: [databind#5475] Also check @JsonDeserializeAs
1288+
if (keyClass == null && jsonDeserAs != null) {
1289+
keyClass = _classIfExplicit(jsonDeserAs.keys());
1290+
}
12671291
if ((keyClass != null)
12681292
&& !_primitiveAndWrapper(keyType, keyClass)) {
12691293
try {
@@ -1279,7 +1303,11 @@ public JavaType refineDeserializationType(final MapperConfig<?> config,
12791303
JavaType contentType = type.getContentType();
12801304
if (contentType != null) { // collection[like], map[like], array, reference
12811305
// And then value types for all containers:
1282-
final Class<?> contentClass = (jsonDeser == null) ? null : _classIfExplicit(jsonDeser.contentAs());
1306+
Class<?> contentClass = (jsonDeser == null) ? null : _classIfExplicit(jsonDeser.contentAs());
1307+
// 09-Dec-2025, tatu: [databind#5475] Also check @JsonDeserializeAs
1308+
if (contentClass == null && jsonDeserAs != null) {
1309+
contentClass = _classIfExplicit(jsonDeserAs.content());
1310+
}
12831311
if ((contentClass != null)
12841312
&& !_primitiveAndWrapper(contentType, contentClass)) {
12851313
try {

0 commit comments

Comments
 (0)