Skip to content

Commit c5c6d87

Browse files
committed
Merge branch '2.x' into 3.x
2 parents 4814e93 + f8573f8 commit c5c6d87

13 files changed

Lines changed: 701 additions & 1 deletion

File tree

release-notes/VERSION-2.x

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,21 @@ Active maintainers:
2323
=== Releases ===
2424
------------------------------------------------------------------------
2525

26-
2.21.0 (not yet released)
26+
2.21.0 (18-Jan-2026)
2727

2828
No changes since 2.20
2929

30+
2.20.1 (30-Oct-2025)
31+
32+
No changes since 2.20.0
33+
3034
2.20.0 (28-Aug-2025)
3135

3236
#303: Add unit tests to verify goodness of SPI metadata for Modules
3337
- Generate SBOMs [JSTEP-14]
3438

39+
2.19.4 (29-Oct-2025)
40+
2.19.3 (29-Oct-2025)
3541
2.19.2 (18-Jul-2025)
3642
2.19.1 (13-Jun-2025)
3743

@@ -46,6 +52,7 @@ No changes since 2.19.0
4652
(reported, fix contributed by Apoorva M)
4753
#290: Update Paranamer dep from 2.8 to 2.8.3
4854

55+
2.18.5 (27-Oct-2025)
4956
2.18.4 (06-May-2025)
5057
2.18.3 (28-Feb-2025)
5158
2.18.2 (27-Nov-2024)

spi-subtypes/README.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# jackson-module-spi-subtypes
2+
3+
Registering subtypes without annotating the parent class,
4+
see [this](https://github.com/FasterXML/jackson-databind/issues/2104).
5+
6+
Implementation on SPI.
7+
8+
# Usage
9+
10+
Registering modules.
11+
12+
```
13+
ObjectMapper mapper = JsonMapper.builder()
14+
.addModule(new SubtypesModule())
15+
.build();
16+
```
17+
18+
Ensure that the parent class has at least the `JsonTypeInfo` annotation.
19+
20+
```java
21+
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
22+
public interface Parent {
23+
}
24+
```
25+
26+
1. add the `JacksonSubType` annotation to your subclass.
27+
2. provide a non-argument constructor (SPI requires it).
28+
29+
```java
30+
package org.example;
31+
32+
import com.fasterxml.jackson.module.spisubtypes.JacksonSubType;
33+
34+
@JacksonSubType("first-child")
35+
public class FirstChild {
36+
37+
private String foo;
38+
// ...
39+
40+
public FirstChild() {
41+
}
42+
}
43+
```
44+
45+
SPI: Put the subclasses in the `META-INF/services` directory under the interface.
46+
Example: `META-INF/services/org.example.Parent`
47+
48+
```
49+
org.example.FirstChild
50+
```
51+
52+
Alternatively, you can also use the `auto-service` to auto-generate these files:
53+
54+
```java
55+
package org.example;
56+
57+
import com.fasterxml.jackson.module.spisubtypes.JacksonSubType;
58+
import com.google.auto.service.AutoService;
59+
60+
@AutoService(Parent.class)
61+
@JacksonSubType("first-child")
62+
public class FirstChild {
63+
64+
private String foo;
65+
// ...
66+
67+
public FirstChild() {
68+
}
69+
}
70+
```
71+
72+
Done, enjoy it.

spi-subtypes/pom.xml

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
3+
<!-- This module was also published with a richer model, Gradle metadata, -->
4+
<!-- which should be used instead. Do not delete the following line which -->
5+
<!-- is to indicate to Gradle or any Gradle module metadata file consumer -->
6+
<!-- that they should prefer consuming it instead. -->
7+
<!-- do_not_remove: published-with-gradle-metadata -->
8+
<modelVersion>4.0.0</modelVersion>
9+
<parent>
10+
<groupId>com.fasterxml.jackson.module</groupId>
11+
<artifactId>jackson-modules-base</artifactId>
12+
<version>2.22.0-SNAPSHOT</version>
13+
</parent>
14+
<artifactId>jackson-module-spi-subtypes</artifactId>
15+
<name>Jackson module: SPI Subtypes Annotation Support</name>
16+
<packaging>bundle</packaging>
17+
18+
<description>Registering subtypes through SPI without annotating the parent class.</description>
19+
<url>https://github.com/FasterXML/jackson-modules-base</url>
20+
21+
<licenses>
22+
<license>
23+
<name>The Apache Software License, Version 2.0</name>
24+
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
25+
<distribution>repo</distribution>
26+
</license>
27+
</licenses>
28+
29+
<properties>
30+
<!-- Generate PackageVersion.java into this directory. -->
31+
<packageVersion.dir>com/fasterxml/jackson/module/spisubtypes</packageVersion.dir>
32+
<packageVersion.package>com.fasterxml.jackson.module.spisubtypes</packageVersion.package>
33+
</properties>
34+
35+
<dependencies>
36+
<dependency>
37+
<groupId>com.fasterxml.jackson.core</groupId>
38+
<artifactId>jackson-core</artifactId>
39+
</dependency>
40+
<dependency>
41+
<groupId>com.fasterxml.jackson.core</groupId>
42+
<artifactId>jackson-databind</artifactId>
43+
</dependency>
44+
<dependency>
45+
<groupId>com.google.auto.service</groupId>
46+
<artifactId>auto-service</artifactId>
47+
<version>1.1.1</version>
48+
<scope>test</scope>
49+
</dependency>
50+
</dependencies>
51+
52+
<build>
53+
<plugins>
54+
<plugin>
55+
<groupId>com.google.code.maven-replacer-plugin</groupId>
56+
<artifactId>replacer</artifactId>
57+
</plugin>
58+
<!-- 14-Mar-2019, tatu: Add rudimentary JDK9+ module info. To build with JDK 8
59+
will have to use `moduleInfoFile` as anything else requires JDK 9+
60+
-->
61+
<plugin>
62+
<groupId>org.moditect</groupId>
63+
<artifactId>moditect-maven-plugin</artifactId>
64+
</plugin>
65+
<plugin>
66+
<groupId>org.apache.maven.plugins</groupId>
67+
<artifactId>maven-compiler-plugin</artifactId>
68+
</plugin>
69+
</plugins>
70+
</build>
71+
</project>
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.fasterxml.jackson.module.spisubtypes;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
import com.fasterxml.jackson.annotation.JacksonAnnotation;
9+
import com.fasterxml.jackson.annotation.JsonSubTypes;
10+
import com.fasterxml.jackson.annotation.JsonTypeName;
11+
12+
/**
13+
* Definition of a subtype, along with optional name(s). If no name is defined
14+
* (empty Strings are ignored), class of the type will be checked for {@link JsonTypeName}
15+
* annotation; and if that is also missing or empty, a default
16+
* name will be constructed by type id mechanism.
17+
* Default name is usually based on class name.
18+
* <p>
19+
* It's the same as {@link JsonSubTypes.Type}.
20+
*
21+
* @since 2.21 / 3.1
22+
*/
23+
@Target(ElementType.TYPE)
24+
@Retention(RetentionPolicy.RUNTIME)
25+
@JacksonAnnotation
26+
public @interface JacksonSubType {
27+
/**
28+
* Logical type name used as the type identifier for the class, if defined; empty
29+
* String means "not defined". Used unless {@link #names} is defined as non-empty.
30+
*
31+
* @return subtype name
32+
*/
33+
String value() default "";
34+
35+
/**
36+
* (optional) Logical type names used as the type identifier for the class: used if
37+
* more than one type name should be associated with the same type.
38+
*
39+
* @return subtype name array
40+
*/
41+
String[] names() default {};
42+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package @package@;
2+
3+
import com.fasterxml.jackson.core.Version;
4+
import com.fasterxml.jackson.core.Versioned;
5+
import com.fasterxml.jackson.core.util.VersionUtil;
6+
7+
/**
8+
* Automatically generated from PackageVersion.java.in during
9+
* packageVersion-generate execution of maven-replacer-plugin in
10+
* pom.xml.
11+
*/
12+
public final class PackageVersion implements Versioned {
13+
public final static Version VERSION = VersionUtil.parseVersion(
14+
"@projectversion@", "@projectgroupid@", "@projectartifactid@");
15+
16+
@Override
17+
public Version version() {
18+
return VERSION;
19+
}
20+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package com.fasterxml.jackson.module.spisubtypes;
2+
3+
import java.util.ArrayList;
4+
import java.util.Collections;
5+
import java.util.List;
6+
import java.util.ServiceLoader;
7+
import java.util.concurrent.ConcurrentHashMap;
8+
import java.util.function.Function;
9+
10+
import com.fasterxml.jackson.core.Version;
11+
12+
import com.fasterxml.jackson.databind.AnnotationIntrospector;
13+
import com.fasterxml.jackson.databind.introspect.Annotated;
14+
import com.fasterxml.jackson.databind.jsontype.NamedType;
15+
16+
/**
17+
* Annotation introspector that handles {@link JacksonSubType} annotation.
18+
* <p>
19+
* It caches the subclasses of a parent class, so it's not-real-time.
20+
* When the parent class not found in cache,
21+
* it will try to load all found child classes via SPI then cache it.
22+
* </p>
23+
*
24+
* @since 2.21 / 3.1
25+
*/
26+
public class SubtypesAnnotationIntrospector extends AnnotationIntrospector {
27+
private final ConcurrentHashMap<Class<?>, List<NamedType>> subtypes = new ConcurrentHashMap<>();
28+
29+
@Override
30+
public Version version() {
31+
return PackageVersion.VERSION;
32+
}
33+
34+
@Override
35+
public List<NamedType> findSubtypes(Annotated a) {
36+
registerTypes(a.getRawType());
37+
38+
List<NamedType> list1 = _findSubtypes(a.getRawType(), a::getAnnotation);
39+
List<NamedType> list2 = subtypes.getOrDefault(a.getRawType(), Collections.emptyList());
40+
41+
if (list1.isEmpty()) return list2;
42+
if (list2.isEmpty()) return list1;
43+
List<NamedType> list = new ArrayList<>(list1.size() + list2.size());
44+
list.addAll(list1);
45+
list.addAll(list2);
46+
return list;
47+
}
48+
49+
/**
50+
* load parent's subclass by SPI.
51+
*
52+
* @param parent parent class.
53+
* @param <S> parent class type.
54+
*/
55+
@SuppressWarnings("unchecked")
56+
private <S> void registerTypes(Class<S> parent) {
57+
// If parent is already registered (either by spi or manually by the user), then skip it
58+
if (subtypes.containsKey(parent)) {
59+
return;
60+
}
61+
List<NamedType> result = new ArrayList<>();
62+
for (S instance : ServiceLoader.load(parent)) {
63+
Class<S> subclass = (Class<S>) instance.getClass();
64+
result.addAll(_findSubtypes(subclass, subclass::getAnnotation));
65+
}
66+
subtypes.put(parent, result);
67+
}
68+
69+
/**
70+
* find all {@link JacksonSubType} names.
71+
*
72+
* @param clazz class which annotate with {@link JacksonSubType}.
73+
* @param getter getAnnotation.
74+
* @param <S> class type.
75+
* @return all names.
76+
*/
77+
private <S> List<NamedType> _findSubtypes(Class<S> clazz, Function<Class<JacksonSubType>, JacksonSubType> getter) {
78+
if (clazz == null) {
79+
return Collections.emptyList();
80+
}
81+
JacksonSubType subtype = getter.apply(JacksonSubType.class);
82+
if (subtype == null) {
83+
return Collections.emptyList();
84+
}
85+
List<NamedType> result = new ArrayList<>();
86+
result.add(new NamedType(clazz, subtype.value()));
87+
// [databind#2761]: alternative set of names to use
88+
for (String name : subtype.names()) {
89+
result.add(new NamedType(clazz, name));
90+
}
91+
return result;
92+
}
93+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.fasterxml.jackson.module.spisubtypes;
2+
3+
import com.fasterxml.jackson.core.Version;
4+
import com.fasterxml.jackson.databind.Module;
5+
6+
/**
7+
* Subtypes module for registering subtypes without annotating the parent class.
8+
* See <a href="https://github.com/FasterXML/jackson-databind/issues/2104">this issues</a> in jackson-databind.
9+
*
10+
* @since 2.21 / 3.1
11+
*/
12+
public class SubtypesModule extends Module {
13+
14+
protected SubtypesAnnotationIntrospector _introspector;
15+
16+
public SubtypesModule() {
17+
this(new SubtypesAnnotationIntrospector());
18+
}
19+
20+
public SubtypesModule(SubtypesAnnotationIntrospector introspector) {
21+
this._introspector = introspector;
22+
}
23+
24+
@Override
25+
public String getModuleName() {
26+
return getClass().getSimpleName();
27+
}
28+
29+
@Override
30+
public Version version() {
31+
return PackageVersion.VERSION;
32+
}
33+
34+
@Override
35+
public void setupModule(SetupContext context) {
36+
context.insertAnnotationIntrospector(_introspector);
37+
}
38+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
This copy of Jackson JSON processor `jackson-module-spi-subtypes` module is licensed under the
2+
Apache (Software) License, version 2.0 ("the License").
3+
See the License for details about distribution rights, and the
4+
specific rights regarding derivative works.
5+
6+
You may obtain a copy of the License at:
7+
8+
http://www.apache.org/licenses/LICENSE-2.0

0 commit comments

Comments
 (0)