Rename Jackson-specific Json… classes to Jackson…

Closes gh-47625
This commit is contained in:
Andy Wilkinson
2025-10-16 13:53:17 +01:00
parent e2433fda5f
commit e221bfdd69
34 changed files with 214 additions and 213 deletions
@@ -25,22 +25,22 @@ Several configuration properties are provided for xref:how-to:spring-mvc.adoc#ho
=== Custom Serializers and Deserializers
If you use Jackson to serialize and deserialize JSON data, you might want to write your own javadoc:tools.jackson.databind.ValueSerializer[] and javadoc:tools.jackson.databind.ValueDeserializer[] classes.
Custom serializers are usually https://github.com/FasterXML/jackson-docs/wiki/JacksonHowToCustomSerializers[registered with Jackson through a module], but Spring Boot provides an alternative javadoc:org.springframework.boot.jackson.JsonComponent[format=annotation] annotation that makes it easier to directly register Spring Beans.
Custom serializers are usually https://github.com/FasterXML/jackson-docs/wiki/JacksonHowToCustomSerializers[registered with Jackson through a module], but Spring Boot provides an alternative javadoc:org.springframework.boot.jackson.JacksonComponent[format=annotation] annotation that makes it easier to directly register Spring Beans.
You can use the javadoc:org.springframework.boot.jackson.JsonComponent[format=annotation] annotation directly on javadoc:tools.jackson.databind.ValueSerializer[], javadoc:tools.jackson.databind.ValueDeserializer[] or javadoc:tools.jackson.databind.KeyDeserializer[] implementations.
You can use the javadoc:org.springframework.boot.jackson.JacksonComponent[format=annotation] annotation directly on javadoc:tools.jackson.databind.ValueSerializer[], javadoc:tools.jackson.databind.ValueDeserializer[] or javadoc:tools.jackson.databind.KeyDeserializer[] implementations.
You can also use it on classes that contain serializers/deserializers as inner classes, as shown in the following example:
include-code::MyJsonComponent[]
include-code::MyJacksonComponent[]
All javadoc:org.springframework.boot.jackson.JsonComponent[format=annotation] beans in the javadoc:org.springframework.context.ApplicationContext[] are automatically registered with Jackson.
Because javadoc:org.springframework.boot.jackson.JsonComponent[format=annotation] is meta-annotated with javadoc:org.springframework.stereotype.Component[format=annotation], the usual component-scanning rules apply.
All javadoc:org.springframework.boot.jackson.JacksonComponent[format=annotation] beans in the javadoc:org.springframework.context.ApplicationContext[] are automatically registered with Jackson.
Because javadoc:org.springframework.boot.jackson.JacksonComponent[format=annotation] is meta-annotated with javadoc:org.springframework.stereotype.Component[format=annotation], the usual component-scanning rules apply.
Spring Boot also provides javadoc:org.springframework.boot.jackson.ObjectValueSerializer[] and javadoc:org.springframework.boot.jackson.ObjectValueDeserializer[] base classes that provide useful alternatives to the standard Jackson versions when serializing objects.
See javadoc:org.springframework.boot.jackson.ObjectValueSerializer[] and javadoc:org.springframework.boot.jackson.ObjectValueDeserializer[] in the API documentation for details.
The example above can be rewritten to use javadoc:org.springframework.boot.jackson.ObjectValueSerializer[] and javadoc:org.springframework.boot.jackson.ObjectValueDeserializer[] as follows:
include-code::object/MyJsonComponent[]
include-code::object/MyJacksonComponent[]
@@ -48,8 +48,8 @@ include-code::object/MyJsonComponent[]
=== Mixins
Jackson has support for mixins that can be used to mix additional annotations into those already declared on a target class.
Spring Boot's Jackson auto-configuration will scan your application's packages for classes annotated with javadoc:org.springframework.boot.jackson.JsonMixin[format=annotation] and register them with the auto-configured javadoc:tools.jackson.databind.json.JsonMapper[].
The registration is performed by Spring Boot's javadoc:org.springframework.boot.jackson.JsonMixinModule[].
Spring Boot's Jackson auto-configuration will scan your application's packages for classes annotated with javadoc:org.springframework.boot.jackson.JacksonMixin[format=annotation] and register them with the auto-configured javadoc:tools.jackson.databind.json.JsonMapper[].
The registration is performed by Spring Boot's javadoc:org.springframework.boot.jackson.JacksonMixinModule[].
@@ -295,7 +295,7 @@ You can use this combination if you are not interested in "`slicing`" your appli
To test that object JSON serialization and deserialization is working as expected, you can use the javadoc:org.springframework.boot.json.test.autoconfigure.JsonTest[format=annotation] annotation from the `spring-boot-test-autoconfigure` module.
javadoc:org.springframework.boot.json.test.autoconfigure.JsonTest[format=annotation] auto-configures the available supported JSON mapper, which can be one of the following libraries:
* Jackson javadoc:tools.jackson.databind.JsonMapper[], any javadoc:org.springframework.boot.jackson.JsonComponent[format=annotation] beans and any Jackson javadoc:tools.jackson.databind.JacksonModule[]
* Jackson javadoc:tools.jackson.databind.JsonMapper[], any javadoc:org.springframework.boot.jackson.JacksonComponent[format=annotation] beans and any Jackson javadoc:tools.jackson.databind.JacksonModule[]
* `Gson`
* `Jsonb`
@@ -325,7 +325,7 @@ include-code::MyJsonAssertJTests[tag=*]
== Auto-configured Spring MVC Tests
To test whether Spring MVC controllers are working as expected, use the javadoc:org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest[format=annotation] annotation from the `spring-boot-webmvc-test` module.
javadoc:org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest[format=annotation] auto-configures the Spring MVC infrastructure and limits scanned beans to javadoc:org.springframework.stereotype.Controller[format=annotation], javadoc:org.springframework.web.bind.annotation.ControllerAdvice[format=annotation], javadoc:org.springframework.boot.jackson.JsonComponent[format=annotation], javadoc:org.springframework.core.convert.converter.Converter[], javadoc:org.springframework.core.convert.converter.GenericConverter[], javadoc:jakarta.servlet.Filter[], javadoc:org.springframework.web.servlet.HandlerInterceptor[], javadoc:org.springframework.web.servlet.config.annotation.WebMvcConfigurer[], javadoc:org.springframework.boot.webmvc.autoconfigure.WebMvcRegistrations[], and javadoc:org.springframework.web.method.support.HandlerMethodArgumentResolver[].
javadoc:org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest[format=annotation] auto-configures the Spring MVC infrastructure and limits scanned beans to javadoc:org.springframework.stereotype.Controller[format=annotation], javadoc:org.springframework.web.bind.annotation.ControllerAdvice[format=annotation], javadoc:org.springframework.boot.jackson.JacksonComponent[format=annotation], javadoc:org.springframework.core.convert.converter.Converter[], javadoc:org.springframework.core.convert.converter.GenericConverter[], javadoc:jakarta.servlet.Filter[], javadoc:org.springframework.web.servlet.HandlerInterceptor[], javadoc:org.springframework.web.servlet.config.annotation.WebMvcConfigurer[], javadoc:org.springframework.boot.webmvc.autoconfigure.WebMvcRegistrations[], and javadoc:org.springframework.web.method.support.HandlerMethodArgumentResolver[].
Regular javadoc:org.springframework.stereotype.Component[format=annotation] and javadoc:org.springframework.boot.context.properties.ConfigurationProperties[format=annotation] beans are not scanned when the javadoc:org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest[format=annotation] annotation is used.
javadoc:org.springframework.boot.context.properties.EnableConfigurationProperties[format=annotation] can be used to include javadoc:org.springframework.boot.context.properties.ConfigurationProperties[format=annotation] beans.
@@ -369,7 +369,7 @@ TIP: Sometimes writing Spring MVC tests is not enough; Spring Boot can help you
== Auto-configured Spring WebFlux Tests
To test that {url-spring-framework-docs}/web-reactive.html[Spring WebFlux] controllers are working as expected, you can use the javadoc:org.springframework.boot.webflux.test.autoconfigure.WebFluxTest[format=annotation] annotation from the `spring-boot-webflux-test` module.
javadoc:org.springframework.boot.webflux.test.autoconfigure.WebFluxTest[format=annotation] auto-configures the Spring WebFlux infrastructure and limits scanned beans to javadoc:org.springframework.stereotype.Controller[format=annotation], javadoc:org.springframework.web.bind.annotation.ControllerAdvice[format=annotation], javadoc:org.springframework.boot.jackson.JsonComponent[format=annotation], javadoc:org.springframework.core.convert.converter.Converter[], javadoc:org.springframework.core.convert.converter.GenericConverter[] and javadoc:org.springframework.web.reactive.config.WebFluxConfigurer[].
javadoc:org.springframework.boot.webflux.test.autoconfigure.WebFluxTest[format=annotation] auto-configures the Spring WebFlux infrastructure and limits scanned beans to javadoc:org.springframework.stereotype.Controller[format=annotation], javadoc:org.springframework.web.bind.annotation.ControllerAdvice[format=annotation], javadoc:org.springframework.boot.jackson.JacksonComponent[format=annotation], javadoc:org.springframework.core.convert.converter.Converter[], javadoc:org.springframework.core.convert.converter.GenericConverter[] and javadoc:org.springframework.web.reactive.config.WebFluxConfigurer[].
Regular javadoc:org.springframework.stereotype.Component[format=annotation] and javadoc:org.springframework.boot.context.properties.ConfigurationProperties[format=annotation] beans are not scanned when the javadoc:org.springframework.boot.webflux.test.autoconfigure.WebFluxTest[format=annotation] annotation is used.
javadoc:org.springframework.boot.context.properties.EnableConfigurationProperties[format=annotation] can be used to include javadoc:org.springframework.boot.context.properties.ConfigurationProperties[format=annotation] beans.
@@ -440,7 +440,7 @@ There are javadoc:org.springframework.graphql.test.tester.GraphQlTester[] varian
Spring Boot helps you to test your {url-spring-graphql-docs}/controllers.html[Spring GraphQL Controllers] with the javadoc:org.springframework.boot.graphql.test.autoconfigure.GraphQlTest[format=annotation] annotation from the `spring-boot-graphql-test` module.
javadoc:org.springframework.boot.graphql.test.autoconfigure.GraphQlTest[format=annotation] auto-configures the Spring GraphQL infrastructure, without any transport nor server being involved.
This limits scanned beans to javadoc:org.springframework.stereotype.Controller[format=annotation], javadoc:org.springframework.graphql.execution.RuntimeWiringConfigurer[], javadoc:org.springframework.boot.jackson.JsonComponent[], javadoc:org.springframework.core.convert.converter.Converter[], javadoc:org.springframework.core.convert.converter.GenericConverter[], javadoc:org.springframework.graphql.execution.DataFetcherExceptionResolver[], javadoc:graphql.execution.instrumentation.Instrumentation[] and javadoc:org.springframework.boot.graphql.autoconfigure.GraphQlSourceBuilderCustomizer[].
This limits scanned beans to javadoc:org.springframework.stereotype.Controller[format=annotation], javadoc:org.springframework.graphql.execution.RuntimeWiringConfigurer[], javadoc:org.springframework.boot.jackson.JacksonComponent[], javadoc:org.springframework.core.convert.converter.Converter[], javadoc:org.springframework.core.convert.converter.GenericConverter[], javadoc:org.springframework.graphql.execution.DataFetcherExceptionResolver[], javadoc:graphql.execution.instrumentation.Instrumentation[] and javadoc:org.springframework.boot.graphql.autoconfigure.GraphQlSourceBuilderCustomizer[].
Regular javadoc:org.springframework.stereotype.Component[format=annotation] and javadoc:org.springframework.boot.context.properties.ConfigurationProperties[format=annotation] beans are not scanned when the javadoc:org.springframework.boot.graphql.test.autoconfigure.GraphQlTest[format=annotation] annotation is used.
javadoc:org.springframework.boot.context.properties.EnableConfigurationProperties[format=annotation] can be used to include javadoc:org.springframework.boot.context.properties.ConfigurationProperties[format=annotation] beans.
@@ -24,10 +24,10 @@ import tools.jackson.databind.SerializationContext;
import tools.jackson.databind.ValueDeserializer;
import tools.jackson.databind.ValueSerializer;
import org.springframework.boot.jackson.JsonComponent;
import org.springframework.boot.jackson.JacksonComponent;
@JsonComponent
public class MyJsonComponent {
@JacksonComponent
public class MyJacksonComponent {
public static class Serializer extends ValueSerializer<MyObject> {
@@ -22,12 +22,12 @@ import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.SerializationContext;
import org.springframework.boot.jackson.JsonComponent;
import org.springframework.boot.jackson.JacksonComponent;
import org.springframework.boot.jackson.ObjectValueDeserializer;
import org.springframework.boot.jackson.ObjectValueSerializer;
@JsonComponent
public class MyJsonComponent {
@JacksonComponent
public class MyJacksonComponent {
public static class Serializer extends ObjectValueSerializer<MyObject> {
@@ -24,10 +24,10 @@ import tools.jackson.databind.SerializationContext
import tools.jackson.databind.ValueDeserializer
import tools.jackson.databind.ValueSerializer
import org.springframework.boot.jackson.JsonComponent
import org.springframework.boot.jackson.JacksonComponent
@JsonComponent
class MyJsonComponent {
@JacksonComponent
class MyJacksonComponent {
class Serializer : ValueSerializer<MyObject>() {
override fun serialize(value: MyObject, jgen: JsonGenerator, serializers: SerializationContext) {
@@ -22,12 +22,12 @@ import tools.jackson.databind.DeserializationContext
import tools.jackson.databind.JsonNode
import tools.jackson.databind.SerializationContext
import org.springframework.boot.jackson.JsonComponent;
import org.springframework.boot.jackson.JacksonComponent;
import org.springframework.boot.jackson.ObjectValueDeserializer
import org.springframework.boot.jackson.ObjectValueSerializer
@JsonComponent
class MyJsonComponent {
@JacksonComponent
class MyJacksonComponent {
class Serializer : ObjectValueSerializer<MyObject>() {
override fun serializeObject(value: MyObject, jgen: JsonGenerator, context: SerializationContext) {
@@ -25,7 +25,7 @@ import graphql.execution.instrumentation.Instrumentation;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.boot.graphql.autoconfigure.GraphQlSourceBuilderCustomizer;
import org.springframework.boot.jackson.JsonComponent;
import org.springframework.boot.jackson.JacksonComponent;
import org.springframework.boot.test.context.filter.annotation.StandardAnnotationCustomizableTypeExcludeFilter;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.GenericConverter;
@@ -50,7 +50,7 @@ class GraphQlTypeExcludeFilter extends StandardAnnotationCustomizableTypeExclude
static {
Set<Class<?>> includes = new LinkedHashSet<>();
includes.add(JsonComponent.class);
includes.add(JacksonComponent.class);
includes.add(RuntimeWiringConfigurer.class);
includes.add(Converter.class);
includes.add(GenericConverter.class);
@@ -32,11 +32,11 @@ import org.springframework.stereotype.Component;
/**
* {@link Component @Component} that provides {@link ValueSerializer},
* {@link ValueDeserializer} or {@link KeyDeserializer} implementations to be registered
* with Jackson when {@link JsonComponentModule} is in use. Can be used to annotate
* with Jackson when {@link JacksonComponentModule} is in use. Can be used to annotate
* implementations directly or a class that contains them as inner-classes. For example:
* <pre class="code">
* &#064;JsonComponent
* public class CustomerJsonComponent {
* &#064;JacksonComponent
* public class CustomerJacksonComponent {
*
* public static class Serializer extends ValueSerializer&lt;Customer&gt; {
*
@@ -54,7 +54,7 @@ import org.springframework.stereotype.Component;
*
* </pre>
*
* @see JsonComponentModule
* @see JacksonComponentModule
* @since 4.0.0
* @author Phillip Webb
* @author Paul Aly
@@ -63,11 +63,11 @@ import org.springframework.stereotype.Component;
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface JsonComponent {
public @interface JacksonComponent {
/**
* The value may indicate a suggestion for a logical component name, to be turned into
* a Spring bean in case of an autodetected component.
* a Spring bean in case of an auto-detected component.
* @return the component name
*/
@AliasFor(annotation = Component.class)
@@ -24,6 +24,7 @@ import java.util.Map;
import java.util.function.BiConsumer;
import org.jspecify.annotations.Nullable;
import tools.jackson.databind.JacksonModule;
import tools.jackson.databind.KeyDeserializer;
import tools.jackson.databind.ValueDeserializer;
import tools.jackson.databind.ValueSerializer;
@@ -43,7 +44,7 @@ import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContrib
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
import org.springframework.beans.factory.aot.BeanFactoryInitializationCode;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.jackson.JsonComponent.Scope;
import org.springframework.boot.jackson.JacksonComponent.Scope;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
@@ -52,15 +53,15 @@ import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* Spring Bean and Jackson {@link Module} to register {@link JsonComponent @JsonComponent}
* annotated beans.
* {@link JacksonModule} to register {@link JacksonComponent @JacksonComponent} annotated
* beans.
*
* @author Phillip Webb
* @author Paul Aly
* @since 4.0.0
* @see JsonComponent
* @see JacksonComponent
*/
public class JsonComponentModule extends SimpleModule implements BeanFactoryAware, InitializingBean {
public class JacksonComponentModule extends SimpleModule implements BeanFactoryAware, InitializingBean {
@SuppressWarnings("NullAway.Init")
private BeanFactory beanFactory;
@@ -72,37 +73,37 @@ public class JsonComponentModule extends SimpleModule implements BeanFactoryAwar
@Override
public void afterPropertiesSet() {
registerJsonComponents();
registerJacksonComponents();
}
public void registerJsonComponents() {
public void registerJacksonComponents() {
BeanFactory beanFactory = this.beanFactory;
while (beanFactory != null) {
if (beanFactory instanceof ListableBeanFactory listableBeanFactory) {
addJsonBeans(listableBeanFactory);
addJackonComponentBeans(listableBeanFactory);
}
beanFactory = (beanFactory instanceof HierarchicalBeanFactory hierarchicalBeanFactory)
? hierarchicalBeanFactory.getParentBeanFactory() : null;
}
}
private void addJsonBeans(ListableBeanFactory beanFactory) {
Map<String, Object> beans = beanFactory.getBeansWithAnnotation(JsonComponent.class);
private void addJackonComponentBeans(ListableBeanFactory beanFactory) {
Map<String, Object> beans = beanFactory.getBeansWithAnnotation(JacksonComponent.class);
for (Object bean : beans.values()) {
addJsonBean(bean);
addJacksonComponentBean(bean);
}
}
private void addJsonBean(Object bean) {
MergedAnnotation<JsonComponent> annotation = MergedAnnotations
private void addJacksonComponentBean(Object bean) {
MergedAnnotation<JacksonComponent> annotation = MergedAnnotations
.from(bean.getClass(), SearchStrategy.TYPE_HIERARCHY)
.get(JsonComponent.class);
.get(JacksonComponent.class);
Class<?>[] types = annotation.getClassArray("type");
Scope scope = annotation.getEnum("scope", JsonComponent.Scope.class);
addJsonBean(bean, types, scope);
Scope scope = annotation.getEnum("scope", JacksonComponent.Scope.class);
addJacksonComponentBean(bean, types, scope);
}
private void addJsonBean(Object bean, Class<?>[] types, Scope scope) {
private void addJacksonComponentBean(Object bean, Class<?>[] types, Scope scope) {
if (bean instanceof ValueSerializer<?> jsonSerializer) {
addValueSerializerBean(jsonSerializer, scope, types);
}
@@ -115,7 +116,7 @@ public class JsonComponentModule extends SimpleModule implements BeanFactoryAwar
for (Class<?> innerClass : bean.getClass().getDeclaredClasses()) {
if (isSuitableInnerClass(innerClass)) {
Object innerInstance = BeanUtils.instantiateClass(innerClass);
addJsonBean(innerInstance, types, scope);
addJacksonComponentBean(innerInstance, types, scope);
}
}
}
@@ -127,7 +128,7 @@ public class JsonComponentModule extends SimpleModule implements BeanFactoryAwar
}
@SuppressWarnings("unchecked")
private <T> void addValueSerializerBean(ValueSerializer<T> serializer, JsonComponent.Scope scope,
private <T> void addValueSerializerBean(ValueSerializer<T> serializer, JacksonComponent.Scope scope,
Class<?>[] types) {
Class<T> baseType = (Class<T>) ResolvableType.forClass(ValueSerializer.class, serializer.getClass())
.resolveGeneric();
@@ -163,15 +164,16 @@ public class JsonComponentModule extends SimpleModule implements BeanFactoryAwar
}
}
static class JsonComponentBeanFactoryInitializationAotProcessor implements BeanFactoryInitializationAotProcessor {
static class JacksonComponentBeanFactoryInitializationAotProcessor
implements BeanFactoryInitializationAotProcessor {
@Override
public @Nullable BeanFactoryInitializationAotContribution processAheadOfTime(
ConfigurableListableBeanFactory beanFactory) {
String[] jsonComponents = beanFactory.getBeanNamesForAnnotation(JsonComponent.class);
String[] jacksonComponents = beanFactory.getBeanNamesForAnnotation(JacksonComponent.class);
Map<Class<?>, List<Class<?>>> innerComponents = new HashMap<>();
for (String jsonComponent : jsonComponents) {
Class<?> type = beanFactory.getType(jsonComponent, true);
for (String jacksonComponent : jacksonComponents) {
Class<?> type = beanFactory.getType(jacksonComponent, true);
Assert.state(type != null, "'type' must not be null");
for (Class<?> declaredClass : type.getDeclaredClasses()) {
if (isSuitableInnerClass(declaredClass)) {
@@ -179,16 +181,16 @@ public class JsonComponentModule extends SimpleModule implements BeanFactoryAwar
}
}
}
return innerComponents.isEmpty() ? null : new JsonComponentAotContribution(innerComponents);
return innerComponents.isEmpty() ? null : new JacksonComponentAotContribution(innerComponents);
}
}
private static final class JsonComponentAotContribution implements BeanFactoryInitializationAotContribution {
private static final class JacksonComponentAotContribution implements BeanFactoryInitializationAotContribution {
private final Map<Class<?>, List<Class<?>>> innerComponents;
private JsonComponentAotContribution(Map<Class<?>, List<Class<?>>> innerComponents) {
private JacksonComponentAotContribution(Map<Class<?>, List<Class<?>>> innerComponents) {
this.innerComponents = innerComponents;
}
@@ -26,32 +26,30 @@ import org.springframework.core.annotation.AliasFor;
/**
* Provides a mixin class implementation that registers with Jackson when using
* {@link JsonMixinModule}.
* {@link JacksonMixinModule}.
*
* @author Guirong Hu
* @see JsonMixinModule
* @see JacksonMixinModule
* @since 4.0.0
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface JsonMixin {
public @interface JacksonMixin {
/**
* Alias for the {@link #type()} attribute. Allows for more concise annotation
* declarations e.g.: {@code @JsonMixin(MyType.class)} instead of
* {@code @JsonMixin(type=MyType.class)}.
* declarations e.g.: {@code @JacksonMixin(MyType.class)} instead of
* {@code @JacksonMixin(type=MyType.class)}.
* @return the mixed-in classes
* @since 2.7.0
*/
@AliasFor("type")
Class<?>[] value() default {};
/**
* The types that are handled by the provided mix-in class. {@link #value()} is an
* The types that are handled by the provided mixin class. {@link #value()} is an
* alias for (and mutually exclusive with) this attribute.
* @return the mixed-in classes
* @since 2.7.0
*/
@AliasFor("value")
Class<?>[] type() default {};
@@ -17,26 +17,26 @@
package org.springframework.boot.jackson;
import org.jspecify.annotations.Nullable;
import tools.jackson.databind.JacksonModule;
import tools.jackson.databind.module.SimpleModule;
/**
* Spring Bean and Jackson {@link Module} to find and
* {@link SimpleModule#setMixInAnnotation(Class, Class) register}
* {@link JsonMixin @JsonMixin}-annotated classes.
* {@link JacksonModule} to find and {@link SimpleModule#setMixInAnnotation(Class, Class)
* register} {@link JacksonMixin @JacksonMixin}-annotated classes.
*
* @author Guirong Hu
* @author Stephane Nicoll
* @since 4.0.0
* @see JsonMixin
* @see JacksonMixin
*/
public class JsonMixinModule extends SimpleModule {
public class JacksonMixinModule extends SimpleModule {
/**
* Register the specified {@link JsonMixinModuleEntries entries}.
* Register the specified {@link JacksonMixinModuleEntries entries}.
* @param entries the entries to register to this instance
* @param classLoader the classloader to use
*/
public void registerEntries(JsonMixinModuleEntries entries, @Nullable ClassLoader classLoader) {
public void registerEntries(JacksonMixinModuleEntries entries, @Nullable ClassLoader classLoader) {
entries.doWithEntry(classLoader, this::setMixInAnnotation);
}
@@ -37,16 +37,16 @@ import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* Provide the mapping of json mixin class to consider.
* Provides the mapping of Jackson mixin classes to consider.
*
* @author Stephane Nicoll
* @since 4.0.0
*/
public final class JsonMixinModuleEntries {
public final class JacksonMixinModuleEntries {
private final Map<Object, Object> entries;
private JsonMixinModuleEntries(Builder builder) {
private JacksonMixinModuleEntries(Builder builder) {
this.entries = new LinkedHashMap<>(builder.entries);
}
@@ -55,25 +55,25 @@ public final class JsonMixinModuleEntries {
* @param mixins a consumer of the builder
* @return an instance with the state of the customized builder.
*/
public static JsonMixinModuleEntries create(Consumer<Builder> mixins) {
public static JacksonMixinModuleEntries create(Consumer<Builder> mixins) {
Builder builder = new Builder();
mixins.accept(builder);
return builder.build();
}
/**
* Scan the classpath for {@link JsonMixin @JsonMixin} in the specified
* Scan the classpath for {@link JacksonMixin @JacksonMixin} in the specified
* {@code basePackages}.
* @param context the application context to use
* @param basePackages the base packages to consider
* @return an instance with the result of the scanning
*/
public static JsonMixinModuleEntries scan(ApplicationContext context, Collection<String> basePackages) {
return JsonMixinModuleEntries.create((builder) -> {
public static JacksonMixinModuleEntries scan(ApplicationContext context, Collection<String> basePackages) {
return JacksonMixinModuleEntries.create((builder) -> {
if (ObjectUtils.isEmpty(basePackages)) {
return;
}
JsonMixinComponentScanner scanner = new JsonMixinComponentScanner();
JacksonMixinComponentScanner scanner = new JacksonMixinComponentScanner();
scanner.setEnvironment(context.getEnvironment());
scanner.setResourceLoader(context);
for (String basePackage : basePackages) {
@@ -90,12 +90,12 @@ public final class JsonMixinModuleEntries {
}
private static void registerMixinClass(Builder builder, Class<?> mixinClass) {
MergedAnnotation<JsonMixin> annotation = MergedAnnotations
MergedAnnotation<JacksonMixin> annotation = MergedAnnotations
.from(mixinClass, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY)
.get(JsonMixin.class);
.get(JacksonMixin.class);
Class<?>[] types = annotation.getClassArray("type");
Assert.state(!ObjectUtils.isEmpty(types),
() -> "@JsonMixin annotation on class '" + mixinClass.getName() + "' does not specify any types");
() -> "@JacksonMixin annotation on class '" + mixinClass.getName() + "' does not specify any types");
for (Class<?> type : types) {
builder.and(type, mixinClass);
}
@@ -118,7 +118,7 @@ public final class JsonMixinModuleEntries {
}
/**
* Builder for {@link JsonMixinModuleEntries}.
* Builder for {@link JacksonMixinModuleEntries}.
*/
public static class Builder {
@@ -150,16 +150,16 @@ public final class JsonMixinModuleEntries {
return this;
}
JsonMixinModuleEntries build() {
return new JsonMixinModuleEntries(this);
JacksonMixinModuleEntries build() {
return new JacksonMixinModuleEntries(this);
}
}
static class JsonMixinComponentScanner extends ClassPathScanningCandidateComponentProvider {
static class JacksonMixinComponentScanner extends ClassPathScanningCandidateComponentProvider {
JsonMixinComponentScanner() {
addIncludeFilter(new AnnotationTypeFilter(JsonMixin.class));
JacksonMixinComponentScanner() {
addIncludeFilter(new AnnotationTypeFilter(JacksonMixin.class));
}
@Override
@@ -38,16 +38,17 @@ import org.springframework.javapoet.ClassName;
import org.springframework.javapoet.CodeBlock;
/**
* {@link BeanRegistrationAotProcessor} that replaces any {@link JsonMixinModuleEntries}
* by an hard-coded equivalent. This has the effect of disabling scanning at runtime.
* {@link BeanRegistrationAotProcessor} that replaces any
* {@link JacksonMixinModuleEntries} with a hard-coded equivalent. This has the effect of
* disabling scanning at runtime.
*
* @author Stephane Nicoll
*/
class JsonMixinModuleEntriesBeanRegistrationAotProcessor implements BeanRegistrationAotProcessor {
class JacksonMixinModuleEntriesBeanRegistrationAotProcessor implements BeanRegistrationAotProcessor {
@Override
public @Nullable BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
if (registeredBean.getBeanClass().equals(JsonMixinModuleEntries.class)) {
if (registeredBean.getBeanClass().equals(JacksonMixinModuleEntries.class)) {
return BeanRegistrationAotContribution
.withCustomCodeFragments((codeFragments) -> new AotContribution(codeFragments, registeredBean));
}
@@ -56,7 +57,7 @@ class JsonMixinModuleEntriesBeanRegistrationAotProcessor implements BeanRegistra
static class AotContribution extends BeanRegistrationCodeFragmentsDecorator {
private static final Class<?> BEAN_TYPE = JsonMixinModuleEntries.class;
private static final Class<?> BEAN_TYPE = JacksonMixinModuleEntries.class;
private final RegisteredBean registeredBean;
@@ -76,15 +77,15 @@ class JsonMixinModuleEntriesBeanRegistrationAotProcessor implements BeanRegistra
@Override
public CodeBlock generateInstanceSupplierCode(GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode, boolean allowDirectSupplierShortcut) {
JsonMixinModuleEntries entries = this.registeredBean.getBeanFactory()
.getBean(this.registeredBean.getBeanName(), JsonMixinModuleEntries.class);
JacksonMixinModuleEntries entries = this.registeredBean.getBeanFactory()
.getBean(this.registeredBean.getBeanName(), JacksonMixinModuleEntries.class);
contributeHints(generationContext.getRuntimeHints(), entries);
GeneratedMethod generatedMethod = beanRegistrationCode.getMethods().add("getInstance", (method) -> {
method.addJavadoc("Get the bean instance for '$L'.", this.registeredBean.getBeanName());
method.addModifiers(Modifier.PRIVATE, Modifier.STATIC);
method.returns(BEAN_TYPE);
CodeBlock.Builder code = CodeBlock.builder();
code.add("return $T.create(", JsonMixinModuleEntries.class).beginControlFlow("(mixins) ->");
code.add("return $T.create(", JacksonMixinModuleEntries.class).beginControlFlow("(mixins) ->");
entries.doWithEntry(this.classLoader, (type, mixin) -> addEntryCode(code, type, mixin));
code.endControlFlow(")");
method.addCode(code.build());
@@ -103,7 +104,7 @@ class JsonMixinModuleEntriesBeanRegistrationAotProcessor implements BeanRegistra
}
}
private void contributeHints(RuntimeHints runtimeHints, JsonMixinModuleEntries entries) {
private void contributeHints(RuntimeHints runtimeHints, JacksonMixinModuleEntries entries) {
Set<Class<?>> mixins = new LinkedHashSet<>();
entries.doWithEntry(this.classLoader, (type, mixin) -> mixins.add(mixin));
new BindingReflectionHintsRegistrar().registerReflectionHints(runtimeHints.reflection(),
@@ -53,9 +53,9 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.jackson.JsonComponentModule;
import org.springframework.boot.jackson.JsonMixinModule;
import org.springframework.boot.jackson.JsonMixinModuleEntries;
import org.springframework.boot.jackson.JacksonComponentModule;
import org.springframework.boot.jackson.JacksonMixinModule;
import org.springframework.boot.jackson.JacksonMixinModuleEntries;
import org.springframework.boot.jackson.autoconfigure.JacksonProperties.ConstructorDetectorStrategy;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
@@ -87,8 +87,8 @@ import org.springframework.util.ReflectionUtils;
public final class JacksonAutoConfiguration {
@Bean
JsonComponentModule jsonComponentModule() {
return new JsonComponentModule();
JacksonComponentModule jsonComponentModule() {
return new JacksonComponentModule();
}
@Bean
@@ -117,17 +117,17 @@ public final class JacksonAutoConfiguration {
static class JacksonMixinConfiguration {
@Bean
static JsonMixinModuleEntries jsonMixinModuleEntries(ApplicationContext context) {
static JacksonMixinModuleEntries jacksonMixinModuleEntries(ApplicationContext context) {
List<String> packages = AutoConfigurationPackages.has(context) ? AutoConfigurationPackages.get(context)
: Collections.emptyList();
return JsonMixinModuleEntries.scan(context, packages);
return JacksonMixinModuleEntries.scan(context, packages);
}
@Bean
JsonMixinModule jsonMixinModule(ApplicationContext context, JsonMixinModuleEntries entries) {
JsonMixinModule jsonMixinModule = new JsonMixinModule();
jsonMixinModule.registerEntries(entries, context.getClassLoader());
return jsonMixinModule;
JacksonMixinModule jacksonMixinModule(ApplicationContext context, JacksonMixinModuleEntries entries) {
JacksonMixinModule jacksonMixinModule = new JacksonMixinModule();
jacksonMixinModule.registerEntries(entries, context.getClassLoader());
return jacksonMixinModule;
}
}
@@ -2,7 +2,7 @@ org.springframework.aot.hint.RuntimeHintsRegistrar=\
org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration$JacksonAutoConfigurationRuntimeHints
org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor=\
org.springframework.boot.jackson.JsonComponentModule$JsonComponentBeanFactoryInitializationAotProcessor
org.springframework.boot.jackson.JacksonComponentModule$JacksonComponentBeanFactoryInitializationAotProcessor
org.springframework.beans.factory.aot.BeanRegistrationAotProcessor=\
org.springframework.boot.jackson.JsonMixinModuleEntriesBeanRegistrationAotProcessor
org.springframework.boot.jackson.JacksonMixinModuleEntriesBeanRegistrationAotProcessor
@@ -1,2 +1,2 @@
org.springframework.boot.jackson.JsonComponent
org.springframework.boot.jackson.JacksonComponent
tools.jackson.databind.JacksonModule
@@ -34,10 +34,10 @@ import org.springframework.aot.test.generate.TestGenerationContext;
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
import org.springframework.beans.factory.aot.BeanFactoryInitializationCode;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.jackson.JsonComponentModule.JsonComponentBeanFactoryInitializationAotProcessor;
import org.springframework.boot.jackson.JsonComponentModuleTests.ComponentWithInnerAbstractClass.AbstractSerializer;
import org.springframework.boot.jackson.JsonComponentModuleTests.ComponentWithInnerAbstractClass.ConcreteSerializer;
import org.springframework.boot.jackson.JsonComponentModuleTests.ComponentWithInnerAbstractClass.NotSuitable;
import org.springframework.boot.jackson.JacksonComponentModule.JacksonComponentBeanFactoryInitializationAotProcessor;
import org.springframework.boot.jackson.JacksonComponentModuleTests.ComponentWithInnerAbstractClass.AbstractSerializer;
import org.springframework.boot.jackson.JacksonComponentModuleTests.ComponentWithInnerAbstractClass.ConcreteSerializer;
import org.springframework.boot.jackson.JacksonComponentModuleTests.ComponentWithInnerAbstractClass.NotSuitable;
import org.springframework.boot.jackson.types.Name;
import org.springframework.boot.jackson.types.NameAndAge;
import org.springframework.boot.jackson.types.NameAndCareer;
@@ -48,13 +48,13 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link JsonComponentModule}.
* Tests for {@link JacksonComponentModule}.
*
* @author Phillip Webb
* @author Vladimir Tsanev
* @author Paul Aly
*/
class JsonComponentModuleTests {
class JacksonComponentModuleTests {
private @Nullable AnnotationConfigApplicationContext context;
@@ -68,30 +68,30 @@ class JsonComponentModuleTests {
@Test
void moduleShouldRegisterSerializers() throws Exception {
load(OnlySerializer.class);
JsonComponentModule module = getContext().getBean(JsonComponentModule.class);
JacksonComponentModule module = getContext().getBean(JacksonComponentModule.class);
assertSerialize(module);
}
@Test
void moduleShouldRegisterDeserializers() throws Exception {
load(OnlyDeserializer.class);
JsonComponentModule module = getContext().getBean(JsonComponentModule.class);
JacksonComponentModule module = getContext().getBean(JacksonComponentModule.class);
assertDeserialize(module);
}
@Test
void moduleShouldRegisterInnerClasses() throws Exception {
load(NameAndAgeJsonComponent.class);
JsonComponentModule module = getContext().getBean(JsonComponentModule.class);
load(NameAndAgeJacksonComponent.class);
JacksonComponentModule module = getContext().getBean(JacksonComponentModule.class);
assertSerialize(module);
assertDeserialize(module);
}
@Test
void moduleShouldAllowInnerAbstractClasses() throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(JsonComponentModule.class,
ComponentWithInnerAbstractClass.class);
JsonComponentModule module = context.getBean(JsonComponentModule.class);
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
JacksonComponentModule.class, ComponentWithInnerAbstractClass.class);
JacksonComponentModule module = context.getBean(JacksonComponentModule.class);
assertSerialize(module);
context.close();
}
@@ -99,29 +99,29 @@ class JsonComponentModuleTests {
@Test
void moduleShouldRegisterKeySerializers() throws Exception {
load(OnlyKeySerializer.class);
JsonComponentModule module = getContext().getBean(JsonComponentModule.class);
JacksonComponentModule module = getContext().getBean(JacksonComponentModule.class);
assertKeySerialize(module);
}
@Test
void moduleShouldRegisterKeyDeserializers() throws Exception {
load(OnlyKeyDeserializer.class);
JsonComponentModule module = getContext().getBean(JsonComponentModule.class);
JacksonComponentModule module = getContext().getBean(JacksonComponentModule.class);
assertKeyDeserialize(module);
}
@Test
void moduleShouldRegisterInnerClassesForKeyHandlers() throws Exception {
load(NameAndAgeJsonKeyComponent.class);
JsonComponentModule module = getContext().getBean(JsonComponentModule.class);
load(NameAndAgeJacksonKeyComponent.class);
JacksonComponentModule module = getContext().getBean(JacksonComponentModule.class);
assertKeySerialize(module);
assertKeyDeserialize(module);
}
@Test
void moduleShouldRegisterOnlyForSpecifiedClasses() throws Exception {
load(NameAndCareerJsonComponent.class);
JsonComponentModule module = getContext().getBean(JsonComponentModule.class);
load(NameAndCareerJacksonComponent.class);
JacksonComponentModule module = getContext().getBean(JacksonComponentModule.class);
assertSerialize(module, new NameAndCareer("spring", "developer"), "{\"name\":\"spring\"}");
assertSerialize(module, NameAndAge.create("spring", 100), "{\"age\":100,\"name\":\"spring\"}");
assertDeserializeForSpecifiedClasses(module);
@@ -131,7 +131,7 @@ class JsonComponentModuleTests {
void aotContributionRegistersReflectionHintsForSuitableInnerClasses() {
load(ComponentWithInnerAbstractClass.class);
ConfigurableListableBeanFactory beanFactory = getContext().getBeanFactory();
BeanFactoryInitializationAotContribution contribution = new JsonComponentBeanFactoryInitializationAotProcessor()
BeanFactoryInitializationAotContribution contribution = new JacksonComponentBeanFactoryInitializationAotProcessor()
.processAheadOfTime(beanFactory);
TestGenerationContext generationContext = new TestGenerationContext();
assertThat(contribution).isNotNull();
@@ -155,7 +155,7 @@ class JsonComponentModuleTests {
private void load(Class<?>... configs) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(configs);
context.register(JsonComponentModule.class);
context.register(JacksonComponentModule.class);
context.refresh();
this.context = context;
}
@@ -177,7 +177,7 @@ class JsonComponentModuleTests {
assertThat(nameAndAge.getAge()).isEqualTo(100);
}
private void assertDeserializeForSpecifiedClasses(JsonComponentModule module) {
private void assertDeserializeForSpecifiedClasses(JacksonComponentModule module) {
JsonMapper mapper = JsonMapper.builder().addModule(module).build();
assertThatExceptionOfType(JacksonException.class)
.isThrownBy(() -> mapper.readValue("{\"name\":\"spring\",\"age\":100}", NameAndAge.class));
@@ -209,20 +209,20 @@ class JsonComponentModuleTests {
return context;
}
@JsonComponent
static class OnlySerializer extends NameAndAgeJsonComponent.Serializer {
@JacksonComponent
static class OnlySerializer extends NameAndAgeJacksonComponent.Serializer {
}
@JsonComponent
static class OnlyDeserializer extends NameAndAgeJsonComponent.Deserializer {
@JacksonComponent
static class OnlyDeserializer extends NameAndAgeJacksonComponent.Deserializer {
}
@JsonComponent
@JacksonComponent
static class ComponentWithInnerAbstractClass {
abstract static class AbstractSerializer extends NameAndAgeJsonComponent.Serializer {
abstract static class AbstractSerializer extends NameAndAgeJacksonComponent.Serializer {
}
@@ -236,13 +236,13 @@ class JsonComponentModuleTests {
}
@JsonComponent(scope = JsonComponent.Scope.KEYS)
static class OnlyKeySerializer extends NameAndAgeJsonKeyComponent.Serializer {
@JacksonComponent(scope = JacksonComponent.Scope.KEYS)
static class OnlyKeySerializer extends NameAndAgeJacksonKeyComponent.Serializer {
}
@JsonComponent(scope = JsonComponent.Scope.KEYS, type = NameAndAge.class)
static class OnlyKeyDeserializer extends NameAndAgeJsonKeyComponent.Deserializer {
@JacksonComponent(scope = JacksonComponent.Scope.KEYS, type = NameAndAge.class)
static class OnlyKeyDeserializer extends NameAndAgeJacksonKeyComponent.Deserializer {
}
@@ -49,12 +49,12 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;
/**
* Tests for {@link JsonMixinModuleEntriesBeanRegistrationAotProcessor}.
* Tests for {@link JacksonMixinModuleEntriesBeanRegistrationAotProcessor}.
*
* @author Stephane Nicoll
*/
@CompileWithForkedClassLoader
class JsonMixinModuleEntriesBeanRegistrationAotProcessorTests {
class JacksonMixinModuleEntriesBeanRegistrationAotProcessorTests {
private final TestGenerationContext generationContext = new TestGenerationContext();
@@ -75,8 +75,8 @@ class JsonMixinModuleEntriesBeanRegistrationAotProcessorTests {
registerEntries(RenameMixInClass.class);
compile((freshContext, compiled) -> {
assertThat(freshContext.getBean(TestConfiguration.class).scanningInvoked).isFalse();
JsonMixinModuleEntries jsonMixinModuleEntries = freshContext.getBean(JsonMixinModuleEntries.class);
assertThat(jsonMixinModuleEntries).extracting("entries", InstanceOfAssertFactories.MAP)
JacksonMixinModuleEntries jacksonMixinModuleEntries = freshContext.getBean(JacksonMixinModuleEntries.class);
assertThat(jacksonMixinModuleEntries).extracting("entries", InstanceOfAssertFactories.MAP)
.containsExactly(entry(Name.class, RenameMixInClass.class),
entry(NameAndAge.class, RenameMixInClass.class));
});
@@ -89,8 +89,8 @@ class JsonMixinModuleEntriesBeanRegistrationAotProcessorTests {
registerEntries(privateMixinClass);
compile((freshContext, compiled) -> {
assertThat(freshContext.getBean(TestConfiguration.class).scanningInvoked).isFalse();
JsonMixinModuleEntries jsonMixinModuleEntries = freshContext.getBean(JsonMixinModuleEntries.class);
assertThat(jsonMixinModuleEntries).extracting("entries", InstanceOfAssertFactories.MAP)
JacksonMixinModuleEntries jacksonMixinModuleEntries = freshContext.getBean(JacksonMixinModuleEntries.class);
assertThat(jacksonMixinModuleEntries).extracting("entries", InstanceOfAssertFactories.MAP)
.containsExactly(entry(Name.class.getName(), privateMixinClass.getName()),
entry(NameAndAge.class.getName(), privateMixinClass.getName()));
});
@@ -136,9 +136,9 @@ class JsonMixinModuleEntriesBeanRegistrationAotProcessorTests {
}
@Bean
JsonMixinModuleEntries jsonMixinModuleEntries(ApplicationContext applicationContext) {
JacksonMixinModuleEntries jacksonMixinModuleEntries(ApplicationContext applicationContext) {
this.scanningInvoked = true;
return JsonMixinModuleEntries.scan(applicationContext, this.packageNames);
return JacksonMixinModuleEntries.scan(applicationContext, this.packageNames);
}
}
@@ -40,11 +40,11 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for {@link JsonMixinModule}.
* Tests for {@link JacksonMixinModule}.
*
* @author Guirong Hu
*/
class JsonMixinModuleTests {
class JacksonMixinModuleTests {
private @Nullable AnnotationConfigApplicationContext context;
@@ -56,40 +56,40 @@ class JsonMixinModuleTests {
}
@Test
void jsonWithModuleEmptyMixInWithEmptyTypesShouldFail() {
void emptyMixInWithEmptyTypesShouldFail() {
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(() -> load(EmptyMixIn.class))
.withMessageContaining("Error creating bean with name 'jsonMixinModule'")
.withStackTraceContaining("@JsonMixin annotation on class "
.withMessageContaining("Error creating bean with name 'jacksonMixinModule'")
.withStackTraceContaining("@JacksonMixin annotation on class "
+ "'org.springframework.boot.jackson.scan.f.EmptyMixIn' does not specify any types");
}
@Test
void jsonWithModuleWithRenameMixInClassShouldBeMixedIn() throws Exception {
void moduleWithRenameMixInClassShouldBeMixedIn() throws Exception {
load(RenameMixInClass.class);
JsonMixinModule module = getContext().getBean(JsonMixinModule.class);
JacksonMixinModule module = getContext().getBean(JacksonMixinModule.class);
assertMixIn(module, new Name("spring"), "{\"username\":\"spring\"}");
assertMixIn(module, NameAndAge.create("spring", 100), "{\"age\":100,\"username\":\"spring\"}");
}
@Test
void jsonWithModuleWithEmptyMixInClassShouldNotBeMixedIn() throws Exception {
void moduleWithEmptyMixInClassShouldNotBeMixedIn() throws Exception {
load(EmptyMixInClass.class);
JsonMixinModule module = getContext().getBean(JsonMixinModule.class);
JacksonMixinModule module = getContext().getBean(JacksonMixinModule.class);
assertMixIn(module, new Name("spring"), "{\"name\":\"spring\"}");
assertMixIn(module, NameAndAge.create("spring", 100), "{\"age\":100,\"name\":\"spring\"}");
}
@Test
void jsonWithModuleWithRenameMixInAbstractClassShouldBeMixedIn() throws Exception {
void moduleWithRenameMixInAbstractClassShouldBeMixedIn() throws Exception {
load(RenameMixInAbstractClass.class);
JsonMixinModule module = getContext().getBean(JsonMixinModule.class);
JacksonMixinModule module = getContext().getBean(JacksonMixinModule.class);
assertMixIn(module, NameAndAge.create("spring", 100), "{\"age\":100,\"username\":\"spring\"}");
}
@Test
void jsonWithModuleWithRenameMixInInterfaceShouldBeMixedIn() throws Exception {
void moduleWithRenameMixInInterfaceShouldBeMixedIn() throws Exception {
load(RenameMixInInterface.class);
JsonMixinModule module = getContext().getBean(JsonMixinModule.class);
JacksonMixinModule module = getContext().getBean(JacksonMixinModule.class);
assertMixIn(module, NameAndAge.create("spring", 100), "{\"age\":100,\"username\":\"spring\"}");
}
@@ -101,18 +101,18 @@ class JsonMixinModuleTests {
private void load(Class<?>... basePackageClasses) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.registerBean(JsonMixinModule.class, () -> createJsonMixinModule(context, basePackageClasses));
context.registerBean(JacksonMixinModule.class, () -> createJacksonMixinModule(context, basePackageClasses));
context.refresh();
this.context = context;
}
private JsonMixinModule createJsonMixinModule(AnnotationConfigApplicationContext context,
private JacksonMixinModule createJacksonMixinModule(AnnotationConfigApplicationContext context,
Class<?>... basePackageClasses) {
List<String> basePackages = Arrays.stream(basePackageClasses).map(ClassUtils::getPackageName).toList();
JsonMixinModuleEntries entries = JsonMixinModuleEntries.scan(context, basePackages);
JsonMixinModule jsonMixinModule = new JsonMixinModule();
jsonMixinModule.registerEntries(entries, context.getClassLoader());
return jsonMixinModule;
JacksonMixinModuleEntries entries = JacksonMixinModuleEntries.scan(context, basePackages);
JacksonMixinModule jacksonMixinModule = new JacksonMixinModule();
jacksonMixinModule.registerEntries(entries, context.getClassLoader());
return jacksonMixinModule;
}
private void assertMixIn(JacksonModule module, Name value, String expectedJson) throws Exception {
@@ -27,12 +27,12 @@ import org.springframework.boot.jackson.types.NameAndAge;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Sample {@link JsonComponent @JsonComponent} used for tests.
* Sample {@link JacksonComponent @JacksonComponent} used for tests.
*
* @author Phillip Webb
*/
@JsonComponent
public class NameAndAgeJsonComponent {
@JacksonComponent
public class NameAndAgeJacksonComponent {
static class Serializer extends ObjectValueSerializer<NameAndAge> {
@@ -25,12 +25,12 @@ import tools.jackson.databind.ValueSerializer;
import org.springframework.boot.jackson.types.NameAndAge;
/**
* Sample {@link JsonComponent @JsonComponent} used for tests.
* Sample {@link JacksonComponent @JacksonComponent} used for tests.
*
* @author Paul Aly
*/
@JsonComponent(type = NameAndAge.class, scope = JsonComponent.Scope.KEYS)
public class NameAndAgeJsonKeyComponent {
@JacksonComponent(type = NameAndAge.class, scope = JacksonComponent.Scope.KEYS)
public class NameAndAgeJacksonKeyComponent {
static class Serializer extends ValueSerializer<NameAndAge> {
@@ -26,12 +26,12 @@ import org.springframework.boot.jackson.types.Name;
import org.springframework.boot.jackson.types.NameAndCareer;
/**
* Sample {@link JsonComponent @JsonComponent} used for tests.
* Sample {@link JacksonComponent @JacksonComponent} used for tests.
*
* @author Paul Aly
*/
@JsonComponent(type = NameAndCareer.class)
public class NameAndCareerJsonComponent {
@JacksonComponent(type = NameAndCareer.class)
public class NameAndCareerJacksonComponent {
static class Serializer extends ObjectValueSerializer<Name> {
@@ -32,7 +32,7 @@ import tools.jackson.databind.json.JsonMapper;
import tools.jackson.databind.module.SimpleModule;
import tools.jackson.databind.node.NullNode;
import org.springframework.boot.jackson.NameAndAgeJsonComponent.Deserializer;
import org.springframework.boot.jackson.NameAndAgeJacksonComponent.Deserializer;
import org.springframework.boot.jackson.types.NameAndAge;
import static org.assertj.core.api.Assertions.assertThat;
@@ -52,7 +52,7 @@ class ObjectValueDeserializerTests {
@Test
void deserializeObjectShouldReadJson() throws Exception {
Deserializer deserializer = new NameAndAgeJsonComponent.Deserializer();
Deserializer deserializer = new NameAndAgeJacksonComponent.Deserializer();
SimpleModule module = new SimpleModule();
module.addDeserializer(NameAndAge.class, deserializer);
ObjectMapper mapper = JsonMapper.builder().addModule(module).build();
@@ -20,7 +20,7 @@ import org.junit.jupiter.api.Test;
import tools.jackson.databind.json.JsonMapper;
import tools.jackson.databind.module.SimpleModule;
import org.springframework.boot.jackson.NameAndAgeJsonComponent.Serializer;
import org.springframework.boot.jackson.NameAndAgeJacksonComponent.Serializer;
import org.springframework.boot.jackson.types.NameAndAge;
import static org.assertj.core.api.Assertions.assertThat;
@@ -34,7 +34,7 @@ class ObjectValueSerializerTests {
@Test
void serializeObjectShouldWriteJson() throws Exception {
Serializer serializer = new NameAndAgeJsonComponent.Serializer();
Serializer serializer = new NameAndAgeJacksonComponent.Serializer();
SimpleModule module = new SimpleModule();
module.addSerializer(NameAndAge.class, serializer);
JsonMapper mapper = JsonMapper.builder().addModule(module).build();
@@ -64,10 +64,10 @@ import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
import org.springframework.beans.factory.BeanCurrentlyInCreationException;
import org.springframework.boot.autoconfigure.AutoConfigurationPackage;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.jackson.JsonComponent;
import org.springframework.boot.jackson.JsonMixin;
import org.springframework.boot.jackson.JsonMixinModule;
import org.springframework.boot.jackson.JsonMixinModuleEntries;
import org.springframework.boot.jackson.JacksonComponent;
import org.springframework.boot.jackson.JacksonMixin;
import org.springframework.boot.jackson.JacksonMixinModule;
import org.springframework.boot.jackson.JacksonMixinModuleEntries;
import org.springframework.boot.jackson.ObjectValueSerializer;
import org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration.CborConfiguration.StandardCborMapperBuilderCustomizer;
import org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration.JacksonAutoConfigurationRuntimeHints;
@@ -164,8 +164,8 @@ class JacksonAutoConfigurationTests {
@Test
void jsonMixinModuleShouldBeAutoConfiguredWithBasePackages() {
this.contextRunner.withUserConfiguration(MixinConfiguration.class).run((context) -> {
assertThat(context).hasSingleBean(JsonMixinModule.class).hasSingleBean(JsonMixinModuleEntries.class);
JsonMixinModuleEntries moduleEntries = context.getBean(JsonMixinModuleEntries.class);
assertThat(context).hasSingleBean(JacksonMixinModule.class).hasSingleBean(JacksonMixinModuleEntries.class);
JacksonMixinModuleEntries moduleEntries = context.getBean(JacksonMixinModuleEntries.class);
assertThat(moduleEntries).extracting("entries", InstanceOfAssertFactories.MAP)
.contains(entry(Person.class, EmptyMixin.class));
});
@@ -903,7 +903,7 @@ class JacksonAutoConfigurationTests {
}
@JsonComponent
@JacksonComponent
static class BazSerializer extends ObjectValueSerializer<Baz> {
@Override
@@ -959,7 +959,7 @@ class JacksonAutoConfigurationTests {
}
@JsonMixin(type = Person.class)
@JacksonMixin(type = Person.class)
static class EmptyMixin {
}
@@ -969,7 +969,7 @@ class JacksonAutoConfigurationTests {
}
@JsonComponent
@JacksonComponent
static class CircularDependencySerializer extends ValueSerializer<String> {
CircularDependencySerializer(JsonMapper jsonMapper) {
@@ -25,18 +25,18 @@ import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.SerializationContext;
import org.springframework.boot.jackson.JsonComponent;
import org.springframework.boot.jackson.JacksonComponent;
import org.springframework.boot.jackson.ObjectValueDeserializer;
import org.springframework.boot.jackson.ObjectValueSerializer;
import org.springframework.boot.test.autoconfigure.json.JsonTest;
/**
* Example {@link JsonComponent @JsonComponent} for use with {@link JsonTest @JsonTest}
* Example {@link JacksonComponent @JsonComponent} for use with {@link JsonTest @JsonTest}
* tests.
*
* @author Phillip Webb
*/
@JsonComponent
@JacksonComponent
public class ExampleJsonComponent {
static class Serializer extends ObjectValueSerializer<ExampleCustomObject> {
@@ -18,11 +18,11 @@ package org.springframework.boot.jackson.scan.a;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.boot.jackson.JsonMixin;
import org.springframework.boot.jackson.JacksonMixin;
import org.springframework.boot.jackson.types.Name;
import org.springframework.boot.jackson.types.NameAndAge;
@JsonMixin(type = { Name.class, NameAndAge.class })
@JacksonMixin(type = { Name.class, NameAndAge.class })
public abstract class RenameMixInClass {
@JsonProperty("username")
@@ -18,10 +18,10 @@ package org.springframework.boot.jackson.scan.b;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.boot.jackson.JsonMixin;
import org.springframework.boot.jackson.JacksonMixin;
import org.springframework.boot.jackson.types.NameAndAge;
@JsonMixin(type = NameAndAge.class)
@JacksonMixin(type = NameAndAge.class)
public abstract class RenameMixInAbstractClass {
@JsonProperty("username")
@@ -18,10 +18,10 @@ package org.springframework.boot.jackson.scan.c;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.boot.jackson.JsonMixin;
import org.springframework.boot.jackson.JacksonMixin;
import org.springframework.boot.jackson.types.NameAndAge;
@JsonMixin(type = NameAndAge.class)
@JacksonMixin(type = NameAndAge.class)
public interface RenameMixInInterface {
@JsonProperty("username")
@@ -16,11 +16,11 @@
package org.springframework.boot.jackson.scan.d;
import org.springframework.boot.jackson.JsonMixin;
import org.springframework.boot.jackson.JacksonMixin;
import org.springframework.boot.jackson.types.Name;
import org.springframework.boot.jackson.types.NameAndAge;
@JsonMixin(type = { Name.class, NameAndAge.class })
@JacksonMixin(type = { Name.class, NameAndAge.class })
public class EmptyMixInClass {
}
@@ -18,11 +18,11 @@ package org.springframework.boot.jackson.scan.e;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.boot.jackson.JsonMixin;
import org.springframework.boot.jackson.JacksonMixin;
import org.springframework.boot.jackson.types.Name;
import org.springframework.boot.jackson.types.NameAndAge;
@JsonMixin(type = { Name.class, NameAndAge.class })
@JacksonMixin(type = { Name.class, NameAndAge.class })
class PrivateMixInClass {
@JsonProperty("username")
@@ -18,9 +18,9 @@ package org.springframework.boot.jackson.scan.f;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.boot.jackson.JsonMixin;
import org.springframework.boot.jackson.JacksonMixin;
@JsonMixin
@JacksonMixin
public interface EmptyMixIn {
@JsonProperty("username")
@@ -22,7 +22,7 @@ import java.util.LinkedHashSet;
import java.util.Set;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.boot.jackson.JsonComponent;
import org.springframework.boot.jackson.JacksonComponent;
import org.springframework.boot.test.context.filter.annotation.StandardAnnotationCustomizableTypeExcludeFilter;
import org.springframework.util.ClassUtils;
@@ -49,7 +49,7 @@ class RestClientTypeExcludeFilter extends StandardAnnotationCustomizableTypeExcl
catch (ClassNotFoundException ex) {
throw new IllegalStateException("Failed to load " + DATABIND_MODULE_CLASS_NAME, ex);
}
includes.add(JsonComponent.class);
includes.add(JacksonComponent.class);
}
KNOWN_INCLUDES = Collections.unmodifiableSet(includes);
}
@@ -22,7 +22,7 @@ import java.util.LinkedHashSet;
import java.util.Set;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.boot.jackson.JsonComponent;
import org.springframework.boot.jackson.JacksonComponent;
import org.springframework.boot.test.context.filter.annotation.StandardAnnotationCustomizableTypeExcludeFilter;
import org.springframework.util.ClassUtils;
@@ -49,7 +49,7 @@ class WebClientTypeExcludeFilter extends StandardAnnotationCustomizableTypeExclu
catch (ClassNotFoundException ex) {
throw new IllegalStateException("Failed to load " + DATABIND_MODULE_CLASS_NAME, ex);
}
includes.add(JsonComponent.class);
includes.add(JacksonComponent.class);
}
KNOWN_INCLUDES = Collections.unmodifiableSet(includes);
}