Update Jackson support to require Jackson 3

Closes gh-45535
This commit is contained in:
Andy Wilkinson 2025-07-22 19:36:19 +01:00
parent 8bfb170ebc
commit d353038c58
176 changed files with 1420 additions and 1678 deletions

View File

@ -59,8 +59,6 @@ dependencies {
testImplementation(project(":test-support:spring-boot-gradle-test-support")) testImplementation(project(":test-support:spring-boot-gradle-test-support"))
testImplementation(project(":test-support:spring-boot-test-support")) testImplementation(project(":test-support:spring-boot-test-support"))
testImplementation("com.fasterxml.jackson.core:jackson-databind")
testImplementation("com.fasterxml.jackson.module:jackson-module-parameter-names")
testImplementation("com.tngtech.archunit:archunit-junit5:1.4.0") testImplementation("com.tngtech.archunit:archunit-junit5:1.4.0")
testImplementation("net.java.dev.jna:jna-platform") testImplementation("net.java.dev.jna:jna-platform")
testImplementation("org.apache.commons:commons-compress") testImplementation("org.apache.commons:commons-compress")
@ -70,6 +68,7 @@ dependencies {
testImplementation("org.jetbrains.kotlin:kotlin-compiler-runner:$kotlinVersion") testImplementation("org.jetbrains.kotlin:kotlin-compiler-runner:$kotlinVersion")
testImplementation("org.jetbrains.kotlin:kotlin-daemon-client:$kotlinVersion") testImplementation("org.jetbrains.kotlin:kotlin-daemon-client:$kotlinVersion")
testImplementation("org.tomlj:tomlj:1.0.0") testImplementation("org.tomlj:tomlj:1.0.0")
testImplementation("tools.jackson.core:jackson-databind")
} }
repositories { repositories {

View File

@ -22,9 +22,6 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import com.fasterxml.jackson.annotation.JsonView; import com.fasterxml.jackson.annotation.JsonView;
import com.fasterxml.jackson.core.Versioned;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import com.sun.jna.Platform; import com.sun.jna.Platform;
import io.spring.gradle.dependencymanagement.DependencyManagementPlugin; import io.spring.gradle.dependencymanagement.DependencyManagementPlugin;
import org.antlr.v4.runtime.Lexer; import org.antlr.v4.runtime.Lexer;
@ -39,6 +36,8 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinCompilerPluginSupportPlugin;
import org.jetbrains.kotlin.project.model.LanguageSettings; import org.jetbrains.kotlin.project.model.LanguageSettings;
import org.jetbrains.kotlin.tooling.core.KotlinToolingVersion; import org.jetbrains.kotlin.tooling.core.KotlinToolingVersion;
import org.tomlj.Toml; import org.tomlj.Toml;
import tools.jackson.core.JsonParser;
import tools.jackson.databind.JacksonModule;
import org.springframework.asm.ClassVisitor; import org.springframework.asm.ClassVisitor;
import org.springframework.boot.buildpack.platform.build.BuildRequest; import org.springframework.boot.buildpack.platform.build.BuildRequest;
@ -103,9 +102,8 @@ public class PluginClasspathGradleBuild extends GradleBuild {
classpath.add(new File(pathOfJarContaining(HttpClientConnectionManager.class))); classpath.add(new File(pathOfJarContaining(HttpClientConnectionManager.class)));
classpath.add(new File(pathOfJarContaining(HttpRequest.class))); classpath.add(new File(pathOfJarContaining(HttpRequest.class)));
classpath.add(new File(pathOfJarContaining(HttpVersionPolicy.class))); classpath.add(new File(pathOfJarContaining(HttpVersionPolicy.class)));
classpath.add(new File(pathOfJarContaining(Module.class))); classpath.add(new File(pathOfJarContaining(JacksonModule.class)));
classpath.add(new File(pathOfJarContaining(Versioned.class))); classpath.add(new File(pathOfJarContaining(JsonParser.class)));
classpath.add(new File(pathOfJarContaining(ParameterNamesModule.class)));
classpath.add(new File(pathOfJarContaining("com.github.openjson.JSONObject"))); classpath.add(new File(pathOfJarContaining("com.github.openjson.JSONObject")));
classpath.add(new File(pathOfJarContaining(JsonView.class))); classpath.add(new File(pathOfJarContaining(JsonView.class)));
classpath.add(new File(pathOfJarContaining(Platform.class))); classpath.add(new File(pathOfJarContaining(Platform.class)));

View File

@ -43,9 +43,9 @@ dependencies {
checkstyle("com.puppycrawl.tools:checkstyle:${checkstyle.toolVersion}") checkstyle("com.puppycrawl.tools:checkstyle:${checkstyle.toolVersion}")
checkstyle("io.spring.javaformat:spring-javaformat-checkstyle:${javaFormatVersion}") checkstyle("io.spring.javaformat:spring-javaformat-checkstyle:${javaFormatVersion}")
implementation(platform("com.fasterxml.jackson:jackson-bom:${jacksonVersion}")) implementation(platform("com.fasterxml.jackson:jackson-bom:${jackson2Version}"))
implementation(platform("tools.jackson:jackson-bom:${jacksonVersion}"))
implementation(platform("org.springframework:spring-framework-bom:${springFrameworkVersion}")) implementation(platform("org.springframework:spring-framework-bom:${springFrameworkVersion}"))
implementation("com.fasterxml.jackson.core:jackson-databind")
implementation("com.github.node-gradle:gradle-node-plugin:3.5.1") implementation("com.github.node-gradle:gradle-node-plugin:3.5.1")
implementation("com.gradle:develocity-gradle-plugin:3.17.2") implementation("com.gradle:develocity-gradle-plugin:3.17.2")
implementation("com.tngtech.archunit:archunit:1.4.1") implementation("com.tngtech.archunit:archunit:1.4.1")
@ -65,6 +65,7 @@ dependencies {
implementation("org.springframework:spring-web") implementation("org.springframework:spring-web")
implementation("org.yaml:snakeyaml:${snakeYamlVersion}") implementation("org.yaml:snakeyaml:${snakeYamlVersion}")
implementation("io.spring.gradle.nullability:nullability-plugin:${nullabilityPluginVersion}") implementation("io.spring.gradle.nullability:nullability-plugin:${nullabilityPluginVersion}")
implementation("tools.jackson.core:jackson-databind")
testImplementation(platform("org.junit:junit-bom:${junitJupiterVersion}")) testImplementation(platform("org.junit:junit-bom:${junitJupiterVersion}"))
testImplementation("org.assertj:assertj-core:${assertjVersion}") testImplementation("org.assertj:assertj-core:${assertjVersion}")

View File

@ -6,4 +6,11 @@
<module name="io.spring.javaformat.checkstyle.SpringChecks"> <module name="io.spring.javaformat.checkstyle.SpringChecks">
<property name="excludes" value="com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocPackageCheck" /> <property name="excludes" value="com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocPackageCheck" />
</module> </module>
</module> <module name="com.puppycrawl.tools.checkstyle.TreeWalker">
<module name="com.puppycrawl.tools.checkstyle.checks.imports.IllegalImportCheck">
<property name="regexp" value="true" />
<property name="illegalPkgs"
value="^com\.fasterxml\.jackson(?!\.annotation).*" />
</module>
</module>
</module>

View File

@ -17,8 +17,6 @@
package org.springframework.boot.build; package org.springframework.boot.build;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@ -26,7 +24,6 @@ import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.gradle.node.NodeExtension; import com.github.gradle.node.NodeExtension;
import com.github.gradle.node.npm.task.NpmInstallTask; import com.github.gradle.node.npm.task.NpmInstallTask;
import io.spring.gradle.antora.GenerateAntoraYmlPlugin; import io.spring.gradle.antora.GenerateAntoraYmlPlugin;
@ -44,6 +41,7 @@ import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.Copy; import org.gradle.api.tasks.Copy;
import org.gradle.api.tasks.TaskContainer; import org.gradle.api.tasks.TaskContainer;
import org.gradle.api.tasks.TaskProvider; import org.gradle.api.tasks.TaskProvider;
import tools.jackson.databind.ObjectMapper;
import org.springframework.boot.build.antora.AntoraAsciidocAttributes; import org.springframework.boot.build.antora.AntoraAsciidocAttributes;
import org.springframework.boot.build.antora.GenerateAntoraPlaybook; import org.springframework.boot.build.antora.GenerateAntoraPlaybook;
@ -198,18 +196,13 @@ public class AntoraConventions {
} }
private String getUiBundleUrl(Project project) { private String getUiBundleUrl(Project project) {
try { File packageJson = project.getRootProject().file("antora/package.json");
File packageJson = project.getRootProject().file("antora/package.json"); ObjectMapper objectMapper = new ObjectMapper();
ObjectMapper objectMapper = new ObjectMapper(); Map<?, ?> json = objectMapper.readerFor(Map.class).readValue(packageJson);
Map<?, ?> json = objectMapper.readerFor(Map.class).readValue(packageJson); Map<?, ?> config = (json != null) ? (Map<?, ?>) json.get("config") : null;
Map<?, ?> config = (json != null) ? (Map<?, ?>) json.get("config") : null; String url = (config != null) ? (String) config.get("ui-bundle-url") : null;
String url = (config != null) ? (String) config.get("ui-bundle-url") : null; Assert.state(StringUtils.hasText(url.toString()), "package.json has not ui-bundle-url config");
Assert.state(StringUtils.hasText(url.toString()), "package.json has not ui-bundle-url config"); return url;
return url;
}
catch (IOException ex) {
throw new UncheckedIOException(ex);
}
} }
private void configureNodeExtension(Project project, NodeExtension nodeExtension) { private void configureNodeExtension(Project project, NodeExtension nodeExtension) {

View File

@ -162,10 +162,9 @@ public class AntoraAsciidocAttributes {
attributes.put("version-native-build-tools", (String) this.projectProperties.get("nativeBuildToolsVersion")); attributes.put("version-native-build-tools", (String) this.projectProperties.get("nativeBuildToolsVersion"));
attributes.put("version-graal", (String) this.projectProperties.get("graalVersion")); attributes.put("version-graal", (String) this.projectProperties.get("graalVersion"));
addDependencyVersion(attributes, "jackson-annotations", "com.fasterxml.jackson.core:jackson-annotations"); addDependencyVersion(attributes, "jackson-annotations", "com.fasterxml.jackson.core:jackson-annotations");
addDependencyVersion(attributes, "jackson-core", "com.fasterxml.jackson.core:jackson-core"); addDependencyVersion(attributes, "jackson-core", "tools.jackson.core:jackson-core");
addDependencyVersion(attributes, "jackson-databind", "com.fasterxml.jackson.core:jackson-databind"); addDependencyVersion(attributes, "jackson-databind", "tools.jackson.core:jackson-databind");
addDependencyVersion(attributes, "jackson-dataformat-xml", addDependencyVersion(attributes, "jackson-dataformat-xml", "tools.jackson.dataformat:jackson-dataformat-xml");
"com.fasterxml.jackson.dataformat:jackson-dataformat-xml");
addSpringDataDependencyVersion(attributes, internal, "spring-data-commons"); addSpringDataDependencyVersion(attributes, internal, "spring-data-commons");
addSpringDataDependencyVersion(attributes, internal, "spring-data-couchbase"); addSpringDataDependencyVersion(attributes, internal, "spring-data-couchbase");
addSpringDataDependencyVersion(attributes, internal, "spring-data-cassandra"); addSpringDataDependencyVersion(attributes, internal, "spring-data-cassandra");

View File

@ -25,10 +25,8 @@ import java.net.URI;
import java.util.List; import java.util.List;
import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonSetter; import tools.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.annotation.Nulls; import tools.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
/** /**
* A resolved bom. * A resolved bom.
@ -42,10 +40,9 @@ public record ResolvedBom(Id id, List<ResolvedLibrary> libraries) {
private static final ObjectMapper objectMapper; private static final ObjectMapper objectMapper;
static { static {
ObjectMapper mapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT) objectMapper = JsonMapper.builder()
.setDefaultPropertyInclusion(Include.NON_EMPTY); .changeDefaultPropertyInclusion((value) -> value.withContentInclusion(Include.NON_EMPTY))
mapper.configOverride(List.class).setSetterInfo(JsonSetter.Value.forValueNulls(Nulls.AS_EMPTY)); .build();
objectMapper = mapper;
} }
public static ResolvedBom readFrom(File file) { public static ResolvedBom readFrom(File file) {
@ -58,12 +55,7 @@ public record ResolvedBom(Id id, List<ResolvedLibrary> libraries) {
} }
public void writeTo(Writer writer) { public void writeTo(Writer writer) {
try { objectMapper.writeValue(writer, this);
objectMapper.writeValue(writer, this);
}
catch (IOException ex) {
throw new UncheckedIOException(ex);
}
} }
public record ResolvedLibrary(String name, String version, String versionProperty, List<Id> managedDependencies, public record ResolvedLibrary(String name, String version, String versionProperty, List<Id> managedDependencies,

View File

@ -19,9 +19,8 @@ package org.springframework.boot.build.bom.bomr.github;
import java.util.Base64; import java.util.Base64;
import java.util.Collections; import java.util.Collections;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.DefaultUriBuilderFactory; import org.springframework.web.util.DefaultUriBuilderFactory;
import org.springframework.web.util.UriTemplateHandler; import org.springframework.web.util.UriTemplateHandler;
@ -61,8 +60,7 @@ final class StandardGitHub implements GitHub {
@SuppressWarnings({ "deprecation", "removal" }) @SuppressWarnings({ "deprecation", "removal" })
private RestTemplate createRestTemplate() { private RestTemplate createRestTemplate() {
return new RestTemplate(Collections.singletonList( return new RestTemplate(Collections.singletonList(new JacksonJsonHttpMessageConverter()));
new org.springframework.http.converter.json.MappingJackson2HttpMessageConverter(new ObjectMapper())));
} }
} }

View File

@ -28,9 +28,6 @@ import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.gradle.api.file.FileTree; import org.gradle.api.file.FileTree;
import org.gradle.api.file.RegularFileProperty; import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.InputFiles;
@ -40,6 +37,7 @@ import org.gradle.api.tasks.PathSensitivity;
import org.gradle.api.tasks.SourceTask; import org.gradle.api.tasks.SourceTask;
import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.TaskAction;
import org.gradle.api.tasks.VerificationException; import org.gradle.api.tasks.VerificationException;
import tools.jackson.databind.ObjectMapper;
/** /**
* {@link SourceTask} that checks additional Spring configuration metadata files. * {@link SourceTask} that checks additional Spring configuration metadata files.
@ -65,7 +63,7 @@ public abstract class CheckAdditionalSpringConfigurationMetadata extends SourceT
} }
@TaskAction @TaskAction
void check() throws JsonParseException, IOException { void check() throws IOException {
Report report = createReport(); Report report = createReport();
File reportFile = getReportLocation().get().getAsFile(); File reportFile = getReportLocation().get().getAsFile();
Files.write(reportFile.toPath(), report, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); Files.write(reportFile.toPath(), report, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
@ -76,7 +74,7 @@ public abstract class CheckAdditionalSpringConfigurationMetadata extends SourceT
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private Report createReport() throws IOException, JsonParseException, JsonMappingException { private Report createReport() {
ObjectMapper objectMapper = new ObjectMapper(); ObjectMapper objectMapper = new ObjectMapper();
Report report = new Report(); Report report = new Report();
for (File file : getSource().getFiles()) { for (File file : getSource().getFiles()) {

View File

@ -26,9 +26,6 @@ import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.gradle.api.DefaultTask; import org.gradle.api.DefaultTask;
import org.gradle.api.file.RegularFileProperty; import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.ListProperty;
@ -40,6 +37,7 @@ import org.gradle.api.tasks.PathSensitivity;
import org.gradle.api.tasks.SourceTask; import org.gradle.api.tasks.SourceTask;
import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.TaskAction;
import org.gradle.api.tasks.VerificationException; import org.gradle.api.tasks.VerificationException;
import tools.jackson.databind.ObjectMapper;
/** /**
* {@link SourceTask} that checks {@code spring-configuration-metadata.json} files. * {@link SourceTask} that checks {@code spring-configuration-metadata.json} files.
@ -65,7 +63,7 @@ public abstract class CheckSpringConfigurationMetadata extends DefaultTask {
public abstract ListProperty<String> getExclusions(); public abstract ListProperty<String> getExclusions();
@TaskAction @TaskAction
void check() throws JsonParseException, IOException { void check() throws IOException {
Report report = createReport(); Report report = createReport();
File reportFile = getReportLocation().get().getAsFile(); File reportFile = getReportLocation().get().getAsFile();
Files.write(reportFile.toPath(), report, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); Files.write(reportFile.toPath(), report, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
@ -76,7 +74,7 @@ public abstract class CheckSpringConfigurationMetadata extends DefaultTask {
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private Report createReport() throws IOException, JsonParseException, JsonMappingException { private Report createReport() {
ObjectMapper objectMapper = new ObjectMapper(); ObjectMapper objectMapper = new ObjectMapper();
File file = getMetadataLocation().get().getAsFile(); File file = getMetadataLocation().get().getAsFile();
Report report = new Report(this.projectRoot.relativize(file.toPath())); Report report = new Report(this.projectRoot.relativize(file.toPath()));

View File

@ -17,7 +17,6 @@
package org.springframework.boot.build.context.properties; package org.springframework.boot.build.context.properties;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@ -25,7 +24,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Stream; import java.util.stream.Stream;
import com.fasterxml.jackson.databind.ObjectMapper; import tools.jackson.databind.ObjectMapper;
/** /**
* Configuration properties read from one or more * Configuration properties read from one or more
@ -56,20 +55,15 @@ final class ConfigurationProperties {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
static ConfigurationProperties fromFiles(Iterable<File> files) { static ConfigurationProperties fromFiles(Iterable<File> files) {
try { ObjectMapper objectMapper = new ObjectMapper();
ObjectMapper objectMapper = new ObjectMapper(); List<ConfigurationProperty> properties = new ArrayList<>();
List<ConfigurationProperty> properties = new ArrayList<>(); for (File file : files) {
for (File file : files) { Map<String, Object> json = objectMapper.readValue(file, Map.class);
Map<String, Object> json = objectMapper.readValue(file, Map.class); for (Map<String, Object> property : (List<Map<String, Object>>) json.get("properties")) {
for (Map<String, Object> property : (List<Map<String, Object>>) json.get("properties")) { properties.add(ConfigurationProperty.fromJsonProperties(property));
properties.add(ConfigurationProperty.fromJsonProperties(property));
}
} }
return new ConfigurationProperties(properties);
}
catch (IOException ex) {
throw new RuntimeException("Failed to load configuration metadata", ex);
} }
return new ConfigurationProperties(properties);
} }
} }

View File

@ -77,12 +77,13 @@ url-spring-data-rest-site=https://spring.io/projects/spring-data-rest
url-spring-data-rest-javadoc=https://docs.spring.io/spring-data/rest/docs/{dotxversion-spring-data-rest}/api url-spring-data-rest-javadoc=https://docs.spring.io/spring-data/rest/docs/{dotxversion-spring-data-rest}/api
url-spring-data-site=https://spring.io/projects/spring-data url-spring-data-site=https://spring.io/projects/spring-data
url-jackson-annotations-javadoc=https://javadoc.io/doc/com.fasterxml.jackson.core/jackson-annotations/{version-jackson-annotations} url-jackson-annotations-javadoc=https://javadoc.io/doc/com.fasterxml.jackson.core/jackson-annotations/{version-jackson-annotations}
url-jackson-core-javadoc=https://javadoc.io/doc/com.fasterxml.jackson.core/jackson-core/{version-jackson-core} url-jackson-core-javadoc=https://javadoc.io/doc/tools.jackson.core/jackson-core/{version-jackson-core}
url-jackson-databind-javadoc=https://javadoc.io/doc/com.fasterxml.jackson.core/jackson-databind/{version-jackson-databind} url-jackson-databind-javadoc=https://javadoc.io/doc/tools.jackson.core/jackson-databind/{version-jackson-databind}
url-jackson-dataformat-xml-javadoc=https://javadoc.io/doc/com.fasterxml.jackson.dataformat/jackson-dataformat-xml/{version-jackson-dataformat-xml} url-jackson-dataformat-xml-javadoc=https://javadoc.io/doc/tools.jackson.dataformat/jackson-dataformat-xml/{version-jackson-dataformat-xml}
# === Javadoc Locations === # === Javadoc Locations ===
javadoc-location-com-fasterxml-jackson-annotation={url-jackson-annotations-javadoc}
javadoc-location-org-apache-pulsar-client-api={url-pulsar-client-api-javadoc} javadoc-location-org-apache-pulsar-client-api={url-pulsar-client-api-javadoc}
javadoc-location-org-apache-pulsar-reactive-client-api={url-pulsar-client-reactive-api-javadoc} javadoc-location-org-apache-pulsar-reactive-client-api={url-pulsar-client-reactive-api-javadoc}
javadoc-location-org-springframework-data-cassandra={url-spring-data-cassandra-javadoc} javadoc-location-org-springframework-data-cassandra={url-spring-data-cassandra-javadoc}
@ -99,10 +100,9 @@ javadoc-location-org-springframework-data-neo4j={url-spring-data-neo4j-javadoc}
javadoc-location-org-springframework-data-r2dbc={url-spring-data-r2dbc-javadoc} javadoc-location-org-springframework-data-r2dbc={url-spring-data-r2dbc-javadoc}
javadoc-location-org-springframework-data-redis={url-spring-data-redis-javadoc} javadoc-location-org-springframework-data-redis={url-spring-data-redis-javadoc}
javadoc-location-org-springframework-data-rest={url-spring-data-rest-javadoc} javadoc-location-org-springframework-data-rest={url-spring-data-rest-javadoc}
javadoc-location-com-fasterxml-jackson-annotation={url-jackson-annotations-javadoc} javadoc-location-tools-jackson-core={url-jackson-core-javadoc}
javadoc-location-com-fasterxml-jackson-core={url-jackson-core-javadoc} javadoc-location-tools-jackson-databind={url-jackson-databind-javadoc}
javadoc-location-com-fasterxml-jackson-databind={url-jackson-databind-javadoc} javadoc-location-tools-jackson-dataformat-xml={url-jackson-dataformat-xml-javadoc}
javadoc-location-com-fasterxml-jackson-dataformat-xml={url-jackson-dataformat-xml-javadoc}
# === API References === # === API References ===

View File

@ -280,12 +280,12 @@ class AntoraAsciidocAttributesTests {
addMockTestcontainersVersion(versions, "rabbitmq", version); addMockTestcontainersVersion(versions, "rabbitmq", version);
addMockTestcontainersVersion(versions, "redpanda", version); addMockTestcontainersVersion(versions, "redpanda", version);
addMockTestcontainersVersion(versions, "r2dbc", version); addMockTestcontainersVersion(versions, "r2dbc", version);
addMockJacksonCoreVersion(versions, "jackson-annotations", version); addMockJackson2CoreVersion(versions, "jackson-annotations", version);
addMockJacksonCoreVersion(versions, "jackson-core", version); addMockJacksonCoreVersion(versions, "jackson-core", version);
addMockJacksonCoreVersion(versions, "jackson-databind", version); addMockJacksonCoreVersion(versions, "jackson-databind", version);
versions.put("org.apache.pulsar:pulsar-client-api", version); versions.put("org.apache.pulsar:pulsar-client-api", version);
versions.put("org.apache.pulsar:pulsar-client-reactive-api", version); versions.put("org.apache.pulsar:pulsar-client-reactive-api", version);
versions.put("com.fasterxml.jackson.dataformat:jackson-dataformat-xml", version); versions.put("tools.jackson.dataformat:jackson-dataformat-xml", version);
return versions; return versions;
} }
@ -297,8 +297,12 @@ class AntoraAsciidocAttributesTests {
versions.put("org.testcontainers:" + artifactId, version); versions.put("org.testcontainers:" + artifactId, version);
} }
private void addMockJacksonCoreVersion(Map<String, String> versions, String artifactId, String version) { private void addMockJackson2CoreVersion(Map<String, String> versions, String artifactId, String version) {
versions.put("com.fasterxml.jackson.core:" + artifactId, version); versions.put("com.fasterxml.jackson.core:" + artifactId, version);
} }
private void addMockJacksonCoreVersion(Map<String, String> versions, String artifactId, String version) {
versions.put("tools.jackson.core:" + artifactId, version);
}
} }

View File

@ -28,13 +28,12 @@ dependencies {
dockerTestRuntimeOnly("org.testcontainers:testcontainers") dockerTestRuntimeOnly("org.testcontainers:testcontainers")
implementation("com.fasterxml.jackson.core:jackson-databind")
implementation("com.fasterxml.jackson.module:jackson-module-parameter-names")
implementation("net.java.dev.jna:jna-platform") implementation("net.java.dev.jna:jna-platform")
implementation("org.apache.commons:commons-compress") implementation("org.apache.commons:commons-compress")
implementation("org.apache.httpcomponents.client5:httpclient5") implementation("org.apache.httpcomponents.client5:httpclient5")
implementation("org.springframework:spring-core") implementation("org.springframework:spring-core")
implementation("org.tomlj:tomlj:1.0.0") implementation("org.tomlj:tomlj:1.0.0")
implementation("tools.jackson.core:jackson-databind")
testImplementation(project(":test-support:spring-boot-test-support")) testImplementation(project(":test-support:spring-boot-test-support"))
} }

View File

@ -23,10 +23,10 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import tools.jackson.core.JacksonException;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.node.ObjectNode;
import org.springframework.boot.buildpack.platform.docker.type.Image; import org.springframework.boot.buildpack.platform.docker.type.Image;
import org.springframework.boot.buildpack.platform.docker.type.ImageConfig; import org.springframework.boot.buildpack.platform.docker.type.ImageConfig;
@ -152,7 +152,7 @@ class BuilderMetadata extends MappedObject {
String json = SharedObjectMapper.get().writeValueAsString(getNode()); String json = SharedObjectMapper.get().writeValueAsString(getNode());
update.withLabel(LABEL_NAME, json); update.withLabel(LABEL_NAME, json);
} }
catch (JsonProcessingException ex) { catch (JacksonException ex) {
throw new IllegalStateException(ex); throw new IllegalStateException(ex);
} }
} }
@ -239,7 +239,7 @@ class BuilderMetadata extends MappedObject {
RunImage(JsonNode node) { RunImage(JsonNode node) {
super(node, MethodHandles.lookup()); super(node, MethodHandles.lookup());
this.image = extractImage(); this.image = extractImage();
this.mirrors = childrenAt("/mirrors", JsonNode::asText); this.mirrors = childrenAt("/mirrors", JsonNode::asString);
} }
private String extractImage() { private String extractImage() {
@ -352,7 +352,7 @@ class BuilderMetadata extends MappedObject {
private final ObjectNode copy; private final ObjectNode copy;
private Update(BuilderMetadata source) { private Update(BuilderMetadata source) {
this.copy = source.getNode().deepCopy(); this.copy = (ObjectNode) source.getNode().deepCopy();
} }
private BuilderMetadata run(Consumer<Update> update) { private BuilderMetadata run(Consumer<Update> update) {

View File

@ -21,8 +21,8 @@ import java.lang.invoke.MethodHandles;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import com.fasterxml.jackson.databind.JsonNode;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import tools.jackson.databind.JsonNode;
import org.springframework.boot.buildpack.platform.docker.type.Image; import org.springframework.boot.buildpack.platform.docker.type.Image;
import org.springframework.boot.buildpack.platform.docker.type.ImageConfig; import org.springframework.boot.buildpack.platform.docker.type.ImageConfig;

View File

@ -19,8 +19,8 @@ package org.springframework.boot.buildpack.platform.build;
import java.io.IOException; import java.io.IOException;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import com.fasterxml.jackson.databind.JsonNode;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import tools.jackson.databind.JsonNode;
import org.springframework.boot.buildpack.platform.docker.type.Image; import org.springframework.boot.buildpack.platform.docker.type.Image;
import org.springframework.boot.buildpack.platform.docker.type.ImageConfig; import org.springframework.boot.buildpack.platform.docker.type.ImageConfig;

View File

@ -17,6 +17,7 @@
package org.springframework.boot.buildpack.platform.docker; package org.springframework.boot.buildpack.platform.docker;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.Arrays; import java.util.Arrays;
@ -286,10 +287,13 @@ public class DockerApi {
listener.onStart(); listener.onStart();
try { try {
try (Response response = http().post(loadUri, "application/x-tar", archive::writeTo)) { try (Response response = http().post(loadUri, "application/x-tar", archive::writeTo)) {
jsonStream().get(response.getContent(), LoadImageUpdateEvent.class, (event) -> { InputStream content = response.getContent();
streamListener.onUpdate(event); if (content != null) {
listener.onUpdate(event); jsonStream().get(content, LoadImageUpdateEvent.class, (event) -> {
}); streamListener.onUpdate(event);
listener.onUpdate(event);
});
}
} }
streamListener.assertValidResponseReceived(); streamListener.assertValidResponseReceived();
} }
@ -395,7 +399,7 @@ public class DockerApi {
: buildUrl("/containers/create"); : buildUrl("/containers/create");
try (Response response = http().post(createUri, "application/json", config::writeTo)) { try (Response response = http().post(createUri, "application/json", config::writeTo)) {
return ContainerReference return ContainerReference
.of(SharedObjectMapper.get().readTree(response.getContent()).at("/Id").asText()); .of(SharedObjectMapper.get().readTree(response.getContent()).at("/Id").asString());
} }
} }

View File

@ -18,8 +18,8 @@ package org.springframework.boot.buildpack.platform.docker.configuration;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import com.fasterxml.jackson.databind.JsonNode;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import tools.jackson.databind.JsonNode;
import org.springframework.boot.buildpack.platform.json.MappedObject; import org.springframework.boot.buildpack.platform.json.MappedObject;
import org.springframework.util.Assert; import org.springframework.util.Assert;

View File

@ -28,10 +28,10 @@ import java.util.HexFormat;
import java.util.Map; import java.util.Map;
import java.util.function.Supplier; import java.util.function.Supplier;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.NullNode;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import tools.jackson.core.JacksonException;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.node.NullNode;
import org.springframework.boot.buildpack.platform.json.MappedObject; import org.springframework.boot.buildpack.platform.json.MappedObject;
import org.springframework.boot.buildpack.platform.json.SharedObjectMapper; import org.springframework.boot.buildpack.platform.json.SharedObjectMapper;
@ -120,7 +120,7 @@ final class DockerConfigurationMetadata {
try { try {
return DockerConfig.fromJson(readPathContent(path)); return DockerConfig.fromJson(readPathContent(path));
} }
catch (JsonProcessingException ex) { catch (JacksonException ex) {
throw new IllegalStateException("Error parsing Docker configuration file '" + path + "'", ex); throw new IllegalStateException("Error parsing Docker configuration file '" + path + "'", ex);
} }
} }
@ -142,7 +142,7 @@ final class DockerConfigurationMetadata {
} }
return context; return context;
} }
catch (JsonProcessingException ex) { catch (JacksonException ex) {
throw new IllegalStateException("Error parsing Docker context metadata file '" + metaPath + "'", ex); throw new IllegalStateException("Error parsing Docker context metadata file '" + metaPath + "'", ex);
} }
} }
@ -181,7 +181,7 @@ final class DockerConfigurationMetadata {
super(node, MethodHandles.lookup()); super(node, MethodHandles.lookup());
this.currentContext = valueAt("/currentContext", String.class); this.currentContext = valueAt("/currentContext", String.class);
this.credsStore = valueAt("/credsStore", String.class); this.credsStore = valueAt("/credsStore", String.class);
this.credHelpers = mapAt("/credHelpers", JsonNode::textValue); this.credHelpers = mapAt("/credHelpers", JsonNode::stringValue);
this.auths = mapAt("/auths", Auth::new); this.auths = mapAt("/auths", Auth::new);
} }
@ -201,7 +201,7 @@ final class DockerConfigurationMetadata {
return this.auths; return this.auths;
} }
static DockerConfig fromJson(String json) throws JsonProcessingException { static DockerConfig fromJson(String json) {
return new DockerConfig(SharedObjectMapper.get().readTree(json)); return new DockerConfig(SharedObjectMapper.get().readTree(json));
} }
@ -285,7 +285,7 @@ final class DockerConfigurationMetadata {
return new DockerContext(this.getNode(), tlsPath); return new DockerContext(this.getNode(), tlsPath);
} }
static DockerContext fromJson(String json) throws JsonProcessingException { static DockerContext fromJson(String json) {
return new DockerContext(SharedObjectMapper.get().readTree(json), null); return new DockerContext(SharedObjectMapper.get().readTree(json), null);
} }

View File

@ -19,8 +19,8 @@ package org.springframework.boot.buildpack.platform.docker.configuration;
import java.util.Base64; import java.util.Base64;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import tools.jackson.core.JacksonException;
import org.springframework.boot.buildpack.platform.json.SharedObjectMapper; import org.springframework.boot.buildpack.platform.json.SharedObjectMapper;
@ -44,7 +44,7 @@ class JsonEncodedDockerRegistryAuthentication implements DockerRegistryAuthentic
try { try {
this.authHeader = Base64.getUrlEncoder().encodeToString(SharedObjectMapper.get().writeValueAsBytes(this)); this.authHeader = Base64.getUrlEncoder().encodeToString(SharedObjectMapper.get().writeValueAsBytes(this));
} }
catch (JsonProcessingException ex) { catch (JacksonException ex) {
throw new IllegalStateException("Error creating Docker registry authentication header", ex); throw new IllegalStateException("Error creating Docker registry authentication header", ex);
} }
} }

View File

@ -38,6 +38,7 @@ import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.io.entity.AbstractHttpEntity; import org.apache.hc.core5.http.io.entity.AbstractHttpEntity;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import tools.jackson.core.JacksonException;
import org.springframework.boot.buildpack.platform.io.Content; import org.springframework.boot.buildpack.platform.io.Content;
import org.springframework.boot.buildpack.platform.io.IOConsumer; import org.springframework.boot.buildpack.platform.io.IOConsumer;
@ -196,7 +197,7 @@ abstract class HttpClientTransport implements HttpTransport {
try { try {
return SharedObjectMapper.get().readValue(content, Errors.class); return SharedObjectMapper.get().readValue(content, Errors.class);
} }
catch (IOException ex) { catch (JacksonException ex) {
return null; return null;
} }
} }
@ -209,7 +210,7 @@ abstract class HttpClientTransport implements HttpTransport {
Message message = SharedObjectMapper.get().readValue(content, Message.class); Message message = SharedObjectMapper.get().readValue(content, Message.class);
return (message.getMessage() != null) ? message : null; return (message.getMessage() != null) ? message : null;
} }
catch (IOException ex) { catch (JacksonException ex) {
return null; return null;
} }
} }

View File

@ -18,7 +18,7 @@ package org.springframework.boot.buildpack.platform.docker.type;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import com.fasterxml.jackson.databind.JsonNode; import tools.jackson.databind.JsonNode;
import org.springframework.boot.buildpack.platform.json.MappedObject; import org.springframework.boot.buildpack.platform.json.MappedObject;
import org.springframework.util.Assert; import org.springframework.util.Assert;

View File

@ -26,10 +26,10 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Consumer; import java.util.function.Consumer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.node.ArrayNode;
import tools.jackson.databind.node.ObjectNode;
import org.springframework.boot.buildpack.platform.json.SharedObjectMapper; import org.springframework.boot.buildpack.platform.json.SharedObjectMapper;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -51,7 +51,7 @@ public class ContainerConfig {
ContainerConfig(@Nullable String user, ImageReference image, String command, List<String> args, ContainerConfig(@Nullable String user, ImageReference image, String command, List<String> args,
Map<String, String> labels, List<Binding> bindings, Map<String, String> env, @Nullable String networkMode, Map<String, String> labels, List<Binding> bindings, Map<String, String> env, @Nullable String networkMode,
List<String> securityOptions) throws IOException { List<String> securityOptions) {
Assert.notNull(image, "'image' must not be null"); Assert.notNull(image, "'image' must not be null");
Assert.hasText(command, "'command' must not be empty"); Assert.hasText(command, "'command' must not be empty");
ObjectMapper objectMapper = SharedObjectMapper.get(); ObjectMapper objectMapper = SharedObjectMapper.get();
@ -135,14 +135,9 @@ public class ContainerConfig {
private ContainerConfig run(Consumer<Update> update) { private ContainerConfig run(Consumer<Update> update) {
update.accept(this); update.accept(this);
try { Assert.state(this.command != null, "'command' must not be null");
Assert.state(this.command != null, "'command' must not be null"); return new ContainerConfig(this.user, this.image, this.command, this.args, this.labels, this.bindings,
return new ContainerConfig(this.user, this.image, this.command, this.args, this.labels, this.bindings, this.env, this.networkMode, this.securityOptions);
this.env, this.networkMode, this.securityOptions);
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
} }
/** /**

View File

@ -20,9 +20,9 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.NullNode;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.node.NullNode;
import org.springframework.boot.buildpack.platform.json.MappedObject; import org.springframework.boot.buildpack.platform.json.MappedObject;
import org.springframework.util.Assert; import org.springframework.util.Assert;

View File

@ -23,8 +23,8 @@ import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import com.fasterxml.jackson.databind.JsonNode;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import tools.jackson.databind.JsonNode;
import org.springframework.boot.buildpack.platform.json.MappedObject; import org.springframework.boot.buildpack.platform.json.MappedObject;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -54,7 +54,7 @@ public class Image extends MappedObject {
Image(JsonNode node) { Image(JsonNode node) {
super(node, MethodHandles.lookup()); super(node, MethodHandles.lookup());
this.digests = childrenAt("/RepoDigests", JsonNode::asText); this.digests = childrenAt("/RepoDigests", JsonNode::asString);
this.config = new ImageConfig(getNode().at("/Config")); this.config = new ImageConfig(getNode().at("/Config"));
this.layers = extractLayers(valueAt("/RootFS/Layers", String[].class)); this.layers = extractLayers(valueAt("/RootFS/Layers", String[].class));
this.os = valueAt("/Os", String.class); this.os = valueAt("/Os", String.class);

View File

@ -29,11 +29,11 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.node.ArrayNode;
import tools.jackson.databind.node.ObjectNode;
import org.springframework.boot.buildpack.platform.io.Content; import org.springframework.boot.buildpack.platform.io.Content;
import org.springframework.boot.buildpack.platform.io.IOConsumer; import org.springframework.boot.buildpack.platform.io.IOConsumer;
@ -173,11 +173,11 @@ public class ImageArchive implements TarArchive {
private ObjectNode createConfig(List<LayerId> writtenLayers) { private ObjectNode createConfig(List<LayerId> writtenLayers) {
ObjectNode config = this.objectMapper.createObjectNode(); ObjectNode config = this.objectMapper.createObjectNode();
config.set("Config", this.imageConfig.getNodeCopy()); config.set("Config", this.imageConfig.getNodeCopy());
config.set("Created", config.textNode(getCreatedDate())); config.set("Created", config.stringNode(getCreatedDate()));
config.set("History", createHistory(writtenLayers)); config.set("History", createHistory(writtenLayers));
config.set("Os", config.textNode(this.os)); config.set("Os", config.stringNode(this.os));
config.set("Architecture", config.textNode(this.architecture)); config.set("Architecture", config.stringNode(this.architecture));
config.set("Variant", config.textNode(this.variant)); config.set("Variant", config.stringNode(this.variant));
config.set("RootFS", createRootFs(writtenLayers)); config.set("RootFS", createRootFs(writtenLayers));
return config; return config;
} }
@ -212,7 +212,7 @@ public class ImageArchive implements TarArchive {
private ArrayNode createManifest(String config, List<LayerId> writtenLayers) { private ArrayNode createManifest(String config, List<LayerId> writtenLayers) {
ArrayNode manifest = this.objectMapper.createArrayNode(); ArrayNode manifest = this.objectMapper.createArrayNode();
ObjectNode entry = manifest.addObject(); ObjectNode entry = manifest.addObject();
entry.set("Config", entry.textNode(config)); entry.set("Config", entry.stringNode(config));
entry.set("Layers", getManifestLayers(writtenLayers)); entry.set("Layers", getManifestLayers(writtenLayers));
if (this.tag != null) { if (this.tag != null) {
entry.set("RepoTags", entry.arrayNode().add(this.tag.toString())); entry.set("RepoTags", entry.arrayNode().add(this.tag.toString()));

View File

@ -21,7 +21,7 @@ import java.io.InputStream;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import java.util.List; import java.util.List;
import com.fasterxml.jackson.databind.JsonNode; import tools.jackson.databind.JsonNode;
import org.springframework.boot.buildpack.platform.json.MappedObject; import org.springframework.boot.buildpack.platform.json.MappedObject;
import org.springframework.util.Assert; import org.springframework.util.Assert;

View File

@ -22,7 +22,7 @@ import java.lang.invoke.MethodHandles;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import com.fasterxml.jackson.databind.JsonNode; import tools.jackson.databind.JsonNode;
import org.springframework.boot.buildpack.platform.json.MappedObject; import org.springframework.boot.buildpack.platform.json.MappedObject;

View File

@ -22,9 +22,9 @@ import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.function.Consumer; import java.util.function.Consumer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.node.ObjectNode;
import org.springframework.boot.buildpack.platform.json.MappedObject; import org.springframework.boot.buildpack.platform.json.MappedObject;
@ -111,7 +111,7 @@ public class ImageConfig extends MappedObject {
private final ObjectNode copy; private final ObjectNode copy;
private Update(ImageConfig source) { private Update(ImageConfig source) {
this.copy = source.getNode().deepCopy(); this.copy = (ObjectNode) source.getNode().deepCopy();
} }
private ImageConfig run(Consumer<Update> update) { private ImageConfig run(Consumer<Update> update) {

View File

@ -21,8 +21,8 @@ import java.io.InputStream;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import java.util.List; import java.util.List;
import com.fasterxml.jackson.databind.JsonNode;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import tools.jackson.databind.JsonNode;
import org.springframework.boot.buildpack.platform.json.MappedObject; import org.springframework.boot.buildpack.platform.json.MappedObject;
import org.springframework.util.Assert; import org.springframework.util.Assert;

View File

@ -22,8 +22,8 @@ import java.lang.invoke.MethodHandles;
import java.util.List; import java.util.List;
import java.util.stream.Stream; import java.util.stream.Stream;
import com.fasterxml.jackson.databind.JsonNode;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import tools.jackson.databind.JsonNode;
import org.springframework.boot.buildpack.platform.json.MappedObject; import org.springframework.boot.buildpack.platform.json.MappedObject;
import org.springframework.util.Assert; import org.springframework.util.Assert;

View File

@ -20,12 +20,11 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.function.Consumer; import java.util.function.Consumer;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import tools.jackson.core.JsonParser;
import tools.jackson.core.JsonToken;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.node.ObjectNode;
/** /**
* Utility class that allows JSON to be parsed and processed as it's received. * Utility class that allows JSON to be parsed and processed as it's received.
@ -64,8 +63,7 @@ public class JsonStream {
* @throws IOException on IO error * @throws IOException on IO error
*/ */
public <T> void get(InputStream content, Class<T> type, Consumer<T> consumer) throws IOException { public <T> void get(InputStream content, Class<T> type, Consumer<T> consumer) throws IOException {
JsonFactory jsonFactory = this.objectMapper.getFactory(); try (JsonParser parser = this.objectMapper.createParser(content)) {
try (JsonParser parser = jsonFactory.createParser(content)) {
while (!parser.isClosed()) { while (!parser.isClosed()) {
JsonToken token = parser.nextToken(); JsonToken token = parser.nextToken();
if (token != null && token != JsonToken.END_OBJECT) { if (token != null && token != JsonToken.END_OBJECT) {
@ -79,9 +77,9 @@ public class JsonStream {
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private <T> @Nullable T read(JsonParser parser, Class<T> type) throws IOException { private <T> @Nullable T read(JsonParser parser, Class<T> type) {
if (ObjectNode.class.isAssignableFrom(type)) { if (ObjectNode.class.isAssignableFrom(type)) {
ObjectNode node = this.objectMapper.readTree(parser); ObjectNode node = (ObjectNode) this.objectMapper.readTree(parser);
if (node == null || node.isMissingNode() || node.isEmpty()) { if (node == null || node.isMissingNode() || node.isEmpty()) {
return null; return null;
} }

View File

@ -30,9 +30,10 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import tools.jackson.core.JacksonException;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.ObjectMapper;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StreamUtils; import org.springframework.util.StreamUtils;
@ -111,7 +112,7 @@ public class MappedObject {
return Collections.emptyList(); return Collections.emptyList();
} }
List<T> children = new ArrayList<>(); List<T> children = new ArrayList<>();
node.elements().forEachRemaining((childNode) -> children.add(factory.apply(childNode))); node.values().forEach((childNode) -> children.add(factory.apply(childNode)));
return Collections.unmodifiableList(children); return Collections.unmodifiableList(children);
} }
@ -146,7 +147,7 @@ public class MappedObject {
try { try {
return SharedObjectMapper.get().treeToValue(result, type); return SharedObjectMapper.get().treeToValue(result, type);
} }
catch (IOException ex) { catch (JacksonException ex) {
throw new IllegalStateException(ex); throw new IllegalStateException(ex);
} }
} }

View File

@ -16,11 +16,12 @@
package org.springframework.boot.buildpack.platform.json; package org.springframework.boot.buildpack.platform.json;
import com.fasterxml.jackson.databind.DeserializationFeature; import tools.jackson.core.json.JsonWriteFeature;
import com.fasterxml.jackson.databind.ObjectMapper; import tools.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.PropertyNamingStrategies; import tools.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature; import tools.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; import tools.jackson.databind.SerializationFeature;
import tools.jackson.databind.json.JsonMapper;
/** /**
* Provides access to a shared pre-configured {@link ObjectMapper}. * Provides access to a shared pre-configured {@link ObjectMapper}.
@ -33,12 +34,12 @@ public final class SharedObjectMapper {
private static final ObjectMapper INSTANCE; private static final ObjectMapper INSTANCE;
static { static {
ObjectMapper objectMapper = new ObjectMapper(); INSTANCE = JsonMapper.builder()
objectMapper.registerModule(new ParameterNamesModule()); .enable(SerializationFeature.INDENT_OUTPUT)
objectMapper.enable(SerializationFeature.INDENT_OUTPUT); .disable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS)
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); .disable(JsonWriteFeature.ESCAPE_FORWARD_SLASHES)
objectMapper.setPropertyNamingStrategy(PropertyNamingStrategies.LOWER_CAMEL_CASE); .propertyNamingStrategy(PropertyNamingStrategies.LOWER_CAMEL_CASE)
INSTANCE = objectMapper; .build();
} }
private SharedObjectMapper() { private SharedObjectMapper() {

View File

@ -26,9 +26,6 @@ import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.sun.jna.Platform; import com.sun.jna.Platform;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
@ -37,6 +34,8 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.mockito.stubbing.Answer; import org.mockito.stubbing.Answer;
import org.skyscreamer.jsonassert.JSONAssert; import org.skyscreamer.jsonassert.JSONAssert;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.node.ArrayNode;
import org.springframework.boot.buildpack.platform.docker.DockerApi; import org.springframework.boot.buildpack.platform.docker.DockerApi;
import org.springframework.boot.buildpack.platform.docker.DockerApi.ContainerApi; import org.springframework.boot.buildpack.platform.docker.DockerApi.ContainerApi;
@ -482,7 +481,7 @@ class LifecycleTests {
return (invocation) -> { return (invocation) -> {
ContainerConfig config = invocation.getArgument(0, ContainerConfig.class); ContainerConfig config = invocation.getArgument(0, ContainerConfig.class);
ArrayNode command = getCommand(config); ArrayNode command = getCommand(config);
String name = command.get(0).asText().substring(1).replaceAll("/", "-"); String name = command.get(0).asString().substring(1).replaceAll("/", "-");
this.configs.put(name, config); this.configs.put(name, config);
if (invocation.getArguments().length > 2) { if (invocation.getArguments().length > 2) {
this.content.put(name, invocation.getArgument(2, ContainerContent.class)); this.content.put(name, invocation.getArgument(2, ContainerContent.class));
@ -491,7 +490,7 @@ class LifecycleTests {
}; };
} }
private ArrayNode getCommand(ContainerConfig config) throws JsonProcessingException { private ArrayNode getCommand(ContainerConfig config) {
JsonNode node = SharedObjectMapper.get().readTree(config.toString()); JsonNode node = SharedObjectMapper.get().readTree(config.toString());
return (ArrayNode) node.at("/Cmd"); return (ArrayNode) node.at("/Cmd");
} }

View File

@ -24,10 +24,10 @@ import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import com.fasterxml.jackson.core.type.TypeReference;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import tools.jackson.core.type.TypeReference;
import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
import org.springframework.boot.buildpack.platform.json.SharedObjectMapper; import org.springframework.boot.buildpack.platform.json.SharedObjectMapper;

View File

@ -16,8 +16,6 @@
package org.springframework.boot.buildpack.platform.docker.type; package org.springframework.boot.buildpack.platform.docker.type;
import java.io.IOException;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.buildpack.platform.json.AbstractJsonTests; import org.springframework.boot.buildpack.platform.json.AbstractJsonTests;
@ -32,7 +30,7 @@ import static org.assertj.core.api.Assertions.assertThat;
class ImageArchiveIndexTests extends AbstractJsonTests { class ImageArchiveIndexTests extends AbstractJsonTests {
@Test @Test
void loadJson() throws IOException { void loadJson() {
String content = getContentAsString("image-archive-index.json"); String content = getContentAsString("image-archive-index.json");
ImageArchiveIndex index = getIndex(content); ImageArchiveIndex index = getIndex(content);
assertThat(index.getSchemaVersion()).isEqualTo(2); assertThat(index.getSchemaVersion()).isEqualTo(2);
@ -43,7 +41,7 @@ class ImageArchiveIndexTests extends AbstractJsonTests {
.isEqualTo("sha256:3bbe02431d8e5124ffe816ec27bf6508b50edd1d10218be1a03e799a186b9004"); .isEqualTo("sha256:3bbe02431d8e5124ffe816ec27bf6508b50edd1d10218be1a03e799a186b9004");
} }
private ImageArchiveIndex getIndex(String content) throws IOException { private ImageArchiveIndex getIndex(String content) {
return new ImageArchiveIndex(getObjectMapper().readTree(content)); return new ImageArchiveIndex(getObjectMapper().readTree(content));
} }

View File

@ -16,7 +16,6 @@
package org.springframework.boot.buildpack.platform.docker.type; package org.springframework.boot.buildpack.platform.docker.type;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -35,7 +34,7 @@ import static org.assertj.core.api.Assertions.assertThat;
class ImageArchiveManifestTests extends AbstractJsonTests { class ImageArchiveManifestTests extends AbstractJsonTests {
@Test @Test
void getLayersReturnsLayers() throws Exception { void getLayersReturnsLayers() {
String content = getContentAsString("image-archive-manifest.json"); String content = getContentAsString("image-archive-manifest.json");
ImageArchiveManifest manifest = getManifest(content); ImageArchiveManifest manifest = getManifest(content);
List<String> expectedLayers = new ArrayList<>(); List<String> expectedLayers = new ArrayList<>();
@ -49,7 +48,7 @@ class ImageArchiveManifestTests extends AbstractJsonTests {
} }
@Test @Test
void getLayersWithNoLayersReturnsEmptyList() throws Exception { void getLayersWithNoLayersReturnsEmptyList() {
String content = "[{\"Layers\": []}]"; String content = "[{\"Layers\": []}]";
ImageArchiveManifest manifest = getManifest(content); ImageArchiveManifest manifest = getManifest(content);
assertThat(manifest.getEntries()).hasSize(1); assertThat(manifest.getEntries()).hasSize(1);
@ -57,13 +56,13 @@ class ImageArchiveManifestTests extends AbstractJsonTests {
} }
@Test @Test
void getLayersWithEmptyManifestReturnsEmptyList() throws Exception { void getLayersWithEmptyManifestReturnsEmptyList() {
String content = "[]"; String content = "[]";
ImageArchiveManifest manifest = getManifest(content); ImageArchiveManifest manifest = getManifest(content);
assertThat(manifest.getEntries()).isEmpty(); assertThat(manifest.getEntries()).isEmpty();
} }
private ImageArchiveManifest getManifest(String content) throws IOException { private ImageArchiveManifest getManifest(String content) {
return new ImageArchiveManifest(getObjectMapper().readTree(content)); return new ImageArchiveManifest(getObjectMapper().readTree(content));
} }

View File

@ -16,7 +16,6 @@
package org.springframework.boot.buildpack.platform.docker.type; package org.springframework.boot.buildpack.platform.docker.type;
import java.io.IOException;
import java.util.Map; import java.util.Map;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -35,7 +34,7 @@ import static org.assertj.core.api.Assertions.entry;
class ImageConfigTests extends AbstractJsonTests { class ImageConfigTests extends AbstractJsonTests {
@Test @Test
void getEnvContainsParsedValues() throws Exception { void getEnvContainsParsedValues() {
ImageConfig imageConfig = getImageConfig(); ImageConfig imageConfig = getImageConfig();
Map<String, String> env = imageConfig.getEnv(); Map<String, String> env = imageConfig.getEnv();
assertThat(env).contains(entry("PATH", "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"), assertThat(env).contains(entry("PATH", "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"),
@ -44,28 +43,28 @@ class ImageConfigTests extends AbstractJsonTests {
} }
@Test @Test
void whenConfigHasNoEnvThenImageConfigEnvIsEmpty() throws Exception { void whenConfigHasNoEnvThenImageConfigEnvIsEmpty() {
ImageConfig imageConfig = getMinimalImageConfig(); ImageConfig imageConfig = getMinimalImageConfig();
Map<String, String> env = imageConfig.getEnv(); Map<String, String> env = imageConfig.getEnv();
assertThat(env).isEmpty(); assertThat(env).isEmpty();
} }
@Test @Test
void whenConfigHasNoLabelsThenImageConfigLabelsIsEmpty() throws Exception { void whenConfigHasNoLabelsThenImageConfigLabelsIsEmpty() {
ImageConfig imageConfig = getMinimalImageConfig(); ImageConfig imageConfig = getMinimalImageConfig();
Map<String, String> env = imageConfig.getLabels(); Map<String, String> env = imageConfig.getLabels();
assertThat(env).isEmpty(); assertThat(env).isEmpty();
} }
@Test @Test
void getLabelsReturnsLabels() throws Exception { void getLabelsReturnsLabels() {
ImageConfig imageConfig = getImageConfig(); ImageConfig imageConfig = getImageConfig();
Map<String, String> labels = imageConfig.getLabels(); Map<String, String> labels = imageConfig.getLabels();
assertThat(labels).hasSize(4).contains(entry("io.buildpacks.stack.id", "org.cloudfoundry.stacks.cflinuxfs3")); assertThat(labels).hasSize(4).contains(entry("io.buildpacks.stack.id", "org.cloudfoundry.stacks.cflinuxfs3"));
} }
@Test @Test
void updateWithLabelUpdatesLabels() throws Exception { void updateWithLabelUpdatesLabels() {
ImageConfig imageConfig = getImageConfig(); ImageConfig imageConfig = getImageConfig();
ImageConfig updatedImageConfig = imageConfig ImageConfig updatedImageConfig = imageConfig
.copy((update) -> update.withLabel("io.buildpacks.stack.id", "test")); .copy((update) -> update.withLabel("io.buildpacks.stack.id", "test"));
@ -74,11 +73,11 @@ class ImageConfigTests extends AbstractJsonTests {
assertThat(updatedImageConfig.getLabels()).hasSize(4).contains(entry("io.buildpacks.stack.id", "test")); assertThat(updatedImageConfig.getLabels()).hasSize(4).contains(entry("io.buildpacks.stack.id", "test"));
} }
private ImageConfig getImageConfig() throws IOException { private ImageConfig getImageConfig() {
return new ImageConfig(getObjectMapper().readTree(getContent("image-config.json"))); return new ImageConfig(getObjectMapper().readTree(getContent("image-config.json")));
} }
private ImageConfig getMinimalImageConfig() throws IOException { private ImageConfig getMinimalImageConfig() {
return new ImageConfig(getObjectMapper().readTree(getContent("minimal-image-config.json"))); return new ImageConfig(getObjectMapper().readTree(getContent("minimal-image-config.json")));
} }

View File

@ -16,8 +16,6 @@
package org.springframework.boot.buildpack.platform.docker.type; package org.springframework.boot.buildpack.platform.docker.type;
import java.io.IOException;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.buildpack.platform.json.AbstractJsonTests; import org.springframework.boot.buildpack.platform.json.AbstractJsonTests;
@ -32,7 +30,7 @@ import static org.assertj.core.api.Assertions.assertThat;
class ManifestListTests extends AbstractJsonTests { class ManifestListTests extends AbstractJsonTests {
@Test @Test
void loadJsonFromDistributionManifestList() throws IOException { void loadJsonFromDistributionManifestList() {
String content = getContentAsString("distribution-manifest-list.json"); String content = getContentAsString("distribution-manifest-list.json");
ManifestList manifestList = getManifestList(content); ManifestList manifestList = getManifestList(content);
assertThat(manifestList.getSchemaVersion()).isEqualTo(2); assertThat(manifestList.getSchemaVersion()).isEqualTo(2);
@ -40,7 +38,7 @@ class ManifestListTests extends AbstractJsonTests {
assertThat(manifestList.getManifests()).hasSize(2); assertThat(manifestList.getManifests()).hasSize(2);
} }
private ManifestList getManifestList(String content) throws IOException { private ManifestList getManifestList(String content) {
return new ManifestList(getObjectMapper().readTree(content)); return new ManifestList(getObjectMapper().readTree(content));
} }

View File

@ -16,8 +16,6 @@
package org.springframework.boot.buildpack.platform.docker.type; package org.springframework.boot.buildpack.platform.docker.type;
import java.io.IOException;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.buildpack.platform.json.AbstractJsonTests; import org.springframework.boot.buildpack.platform.json.AbstractJsonTests;
@ -32,7 +30,7 @@ import static org.assertj.core.api.Assertions.assertThat;
class ManifestTests extends AbstractJsonTests { class ManifestTests extends AbstractJsonTests {
@Test @Test
void loadJsonFromDistributionManifest() throws IOException { void loadJsonFromDistributionManifest() {
String content = getContentAsString("distribution-manifest.json"); String content = getContentAsString("distribution-manifest.json");
Manifest manifestList = getManifest(content); Manifest manifestList = getManifest(content);
assertThat(manifestList.getSchemaVersion()).isEqualTo(2); assertThat(manifestList.getSchemaVersion()).isEqualTo(2);
@ -41,7 +39,7 @@ class ManifestTests extends AbstractJsonTests {
} }
@Test @Test
void loadJsonFromImageManifest() throws IOException { void loadJsonFromImageManifest() {
String content = getContentAsString("image-manifest.json"); String content = getContentAsString("image-manifest.json");
Manifest manifestList = getManifest(content); Manifest manifestList = getManifest(content);
assertThat(manifestList.getSchemaVersion()).isEqualTo(2); assertThat(manifestList.getSchemaVersion()).isEqualTo(2);
@ -49,7 +47,7 @@ class ManifestTests extends AbstractJsonTests {
assertThat(manifestList.getLayers()).hasSize(1); assertThat(manifestList.getLayers()).hasSize(1);
} }
private Manifest getManifest(String content) throws IOException { private Manifest getManifest(String content) {
return new Manifest(getObjectMapper().readTree(content)); return new Manifest(getObjectMapper().readTree(content));
} }

View File

@ -24,7 +24,7 @@ import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import com.fasterxml.jackson.databind.ObjectMapper; import tools.jackson.databind.ObjectMapper;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;

View File

@ -20,8 +20,8 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import tools.jackson.databind.node.ObjectNode;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -44,7 +44,7 @@ class JsonStreamTests extends AbstractJsonTests {
List<ObjectNode> result = new ArrayList<>(); List<ObjectNode> result = new ArrayList<>();
this.jsonStream.get(getContent("stream.json"), result::add); this.jsonStream.get(getContent("stream.json"), result::add);
assertThat(result).hasSize(595); assertThat(result).hasSize(595);
assertThat(result.get(594).toString()) assertThat(result.get(594).get("status").asString())
.contains("Status: Downloaded newer image for paketo-buildpacks/cnb:base"); .contains("Status: Downloaded newer image for paketo-buildpacks/cnb:base");
} }

View File

@ -20,8 +20,8 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import com.fasterxml.jackson.databind.JsonNode;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import tools.jackson.databind.JsonNode;
import org.springframework.boot.buildpack.platform.json.MappedObjectTests.TestMappedObject.Person; import org.springframework.boot.buildpack.platform.json.MappedObjectTests.TestMappedObject.Person;

View File

@ -16,12 +16,11 @@
package org.springframework.boot.buildpack.platform.json; package org.springframework.boot.buildpack.platform.json;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import tools.jackson.databind.DeserializationFeature;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.PropertyNamingStrategies;
import tools.jackson.databind.SerializationFeature;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -36,14 +35,14 @@ class SharedObjectMapperTests {
void getReturnsConfiguredObjectMapper() { void getReturnsConfiguredObjectMapper() {
ObjectMapper mapper = SharedObjectMapper.get(); ObjectMapper mapper = SharedObjectMapper.get();
assertThat(mapper).isNotNull(); assertThat(mapper).isNotNull();
assertThat(mapper.getRegisteredModuleIds()).contains(new ParameterNamesModule().getTypeId()); assertThat(
assertThat(SerializationFeature.INDENT_OUTPUT SerializationFeature.INDENT_OUTPUT.enabledIn(mapper.serializationConfig().getSerializationFeatures()))
.enabledIn(mapper.getSerializationConfig().getSerializationFeatures())).isTrue(); .isTrue();
assertThat(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES assertThat(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
.enabledIn(mapper.getDeserializationConfig().getDeserializationFeatures())).isFalse(); .enabledIn(mapper.deserializationConfig().getDeserializationFeatures())).isFalse();
assertThat(mapper.getSerializationConfig().getPropertyNamingStrategy()) assertThat(mapper.serializationConfig().getPropertyNamingStrategy())
.isEqualTo(PropertyNamingStrategies.LOWER_CAMEL_CASE); .isEqualTo(PropertyNamingStrategies.LOWER_CAMEL_CASE);
assertThat(mapper.getDeserializationConfig().getPropertyNamingStrategy()) assertThat(mapper.deserializationConfig().getPropertyNamingStrategy())
.isEqualTo(PropertyNamingStrategies.LOWER_CAMEL_CASE); .isEqualTo(PropertyNamingStrategies.LOWER_CAMEL_CASE);
} }

View File

@ -45,10 +45,10 @@ dependencies {
testImplementation(project(":test-support:spring-boot-test-support")) testImplementation(project(":test-support:spring-boot-test-support"))
testImplementation(testFixtures(project(":core:spring-boot"))) testImplementation(testFixtures(project(":core:spring-boot")))
testImplementation("ch.qos.logback:logback-classic") testImplementation("ch.qos.logback:logback-classic")
testImplementation("com.fasterxml.jackson.core:jackson-databind")
testImplementation("io.projectreactor:reactor-core") testImplementation("io.projectreactor:reactor-core")
testImplementation("org.springframework:spring-context-support") testImplementation("org.springframework:spring-context-support")
testImplementation("org.springframework.security:spring-security-config") testImplementation("org.springframework.security:spring-security-config")
testImplementation("tools.jackson.core:jackson-databind")
testRuntimeOnly("com.github.ben-manes.caffeine:caffeine") testRuntimeOnly("com.github.ben-manes.caffeine:caffeine")
testRuntimeOnly("org.springframework:spring-webflux") testRuntimeOnly("org.springframework:spring-webflux")

View File

@ -22,6 +22,8 @@ import org.springframework.aop.config.AopConfigUtils;
import org.springframework.aop.framework.autoproxy.AutoProxyUtils; import org.springframework.aop.framework.autoproxy.AutoProxyUtils;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener;
import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.testsupport.classpath.ClassPathExclusions; import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
@ -40,16 +42,18 @@ class NonAspectJAopAutoConfigurationTests {
@Test @Test
void whenAspectJIsAbsentAndProxyTargetClassIsEnabledProxyCreatorBeanIsDefined() { void whenAspectJIsAbsentAndProxyTargetClassIsEnabledProxyCreatorBeanIsDefined() {
this.contextRunner.run((context) -> { this.contextRunner.withInitializer(ConditionEvaluationReportLoggingListener.forLogLevel(LogLevel.INFO))
BeanDefinition defaultProxyConfig = context.getBeanFactory() .run((context) -> {
.getBeanDefinition(AutoProxyUtils.DEFAULT_PROXY_CONFIG_BEAN_NAME); BeanDefinition defaultProxyConfig = context.getBeanFactory()
assertThat(defaultProxyConfig.getPropertyValues().get("proxyTargetClass")).isEqualTo(Boolean.TRUE); .getBeanDefinition(AutoProxyUtils.DEFAULT_PROXY_CONFIG_BEAN_NAME);
}); assertThat(defaultProxyConfig.getPropertyValues().get("proxyTargetClass")).isEqualTo(Boolean.TRUE);
});
} }
@Test @Test
void whenAspectJIsAbsentAndProxyTargetClassIsDisabledNoProxyCreatorBeanIsDefined() { void whenAspectJIsAbsentAndProxyTargetClassIsDisabledNoProxyCreatorBeanIsDefined() {
this.contextRunner.withPropertyValues("spring.aop.proxy-target-class:false") this.contextRunner.withInitializer(ConditionEvaluationReportLoggingListener.forLogLevel(LogLevel.INFO))
.withPropertyValues("spring.aop.proxy-target-class:false")
.run((context) -> assertThat(context).doesNotHaveBean(AutoProxyUtils.DEFAULT_PROXY_CONFIG_BEAN_NAME) .run((context) -> assertThat(context).doesNotHaveBean(AutoProxyUtils.DEFAULT_PROXY_CONFIG_BEAN_NAME)
.doesNotHaveBean(AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME)); .doesNotHaveBean(AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME));
} }

View File

@ -16,8 +16,8 @@
package org.springframework.boot.autoconfigure.condition; package org.springframework.boot.autoconfigure.condition;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import tools.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.condition.OnBeanCondition.BeanTypeDeductionException; import org.springframework.boot.autoconfigure.condition.OnBeanCondition.BeanTypeDeductionException;
import org.springframework.boot.testsupport.classpath.ClassPathExclusions; import org.springframework.boot.testsupport.classpath.ClassPathExclusions;

View File

@ -28,8 +28,7 @@ description = "Spring Boot Docker Compose"
dependencies { dependencies {
api(project(":core:spring-boot-autoconfigure")) api(project(":core:spring-boot-autoconfigure"))
implementation("com.fasterxml.jackson.core:jackson-databind") implementation("tools.jackson.core:jackson-databind")
implementation("com.fasterxml.jackson.module:jackson-module-parameter-names")
dockerTestImplementation(project(":test-support:spring-boot-docker-test-support")) dockerTestImplementation(project(":test-support:spring-boot-docker-test-support"))

View File

@ -16,16 +16,14 @@
package org.springframework.boot.docker.compose.core; package org.springframework.boot.docker.compose.core;
import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import com.fasterxml.jackson.databind.DeserializationFeature; import tools.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType; import tools.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.MapperFeature; import tools.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper; import tools.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper; import tools.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
/** /**
* Support class used to handle JSON returned from the {@link DockerCli}. * Support class used to handle JSON returned from the {@link DockerCli}.
@ -40,7 +38,6 @@ final class DockerJson {
.defaultLocale(Locale.ENGLISH) .defaultLocale(Locale.ENGLISH)
.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) .enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.addModule(new ParameterNamesModule())
.build(); .build();
private DockerJson() { private DockerJson() {
@ -74,12 +71,7 @@ final class DockerJson {
} }
private static <T> T deserialize(String json, JavaType type) { private static <T> T deserialize(String json, JavaType type) {
try { return objectMapper.readValue(json.trim(), type);
return objectMapper.readValue(json.trim(), type);
}
catch (IOException ex) {
throw new DockerOutputParseException(json, ex);
}
} }
} }

View File

@ -64,6 +64,7 @@ class ConnectionNamePredicateTests {
assertThat(predicateOf("redis")).accepts(sourceOf("myhost.com/library/redis")); assertThat(predicateOf("redis")).accepts(sourceOf("myhost.com/library/redis"));
assertThat(predicateOf("redis")).accepts(sourceOf("myhost.com:8080/library/redis")); assertThat(predicateOf("redis")).accepts(sourceOf("myhost.com:8080/library/redis"));
assertThat(predicateOf("redis")).rejects(sourceOf("internalhost:8080/redis")); assertThat(predicateOf("redis")).rejects(sourceOf("internalhost:8080/redis"));
assertThat(predicateOf("redis")).accepts(sourceOf("docker.my-company.com/library/redis:latest"));
} }
@Test @Test

View File

@ -27,7 +27,6 @@ dependencies {
api(project(":core:spring-boot")) api(project(":core:spring-boot"))
api("org.springframework:spring-test") api("org.springframework:spring-test")
optional("com.fasterxml.jackson.core:jackson-databind")
optional("com.google.code.gson:gson") optional("com.google.code.gson:gson")
optional("com.jayway.jsonpath:json-path") optional("com.jayway.jsonpath:json-path")
optional("io.projectreactor.netty:reactor-netty-http") optional("io.projectreactor.netty:reactor-netty-http")
@ -44,6 +43,7 @@ dependencies {
optional("org.springframework:spring-web") optional("org.springframework:spring-web")
optional("org.springframework:spring-webflux") optional("org.springframework:spring-webflux")
optional("org.springframework.graphql:spring-graphql-test") optional("org.springframework.graphql:spring-graphql-test")
optional("tools.jackson.core:jackson-databind")
testImplementation(project(":test-support:spring-boot-test-support")) testImplementation(project(":test-support:spring-boot-test-support"))
testImplementation("ch.qos.logback:logback-classic") testImplementation("ch.qos.logback:logback-classic")

View File

@ -18,16 +18,29 @@ package org.springframework.boot.test.json;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader; import java.io.Reader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.jayway.jsonpath.Configuration; import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.spi.json.JacksonJsonProvider; import com.jayway.jsonpath.InvalidJsonException;
import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider; import com.jayway.jsonpath.TypeRef;
import com.jayway.jsonpath.spi.json.AbstractJsonProvider;
import com.jayway.jsonpath.spi.mapper.MappingException;
import com.jayway.jsonpath.spi.mapper.MappingProvider;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonGenerator;
import tools.jackson.databind.JavaType;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.ObjectReader;
import tools.jackson.databind.ObjectWriter;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.ObjectFactory;
import org.springframework.core.ResolvableType; import org.springframework.core.ResolvableType;
@ -35,14 +48,14 @@ import org.springframework.util.Assert;
/** /**
* AssertJ based JSON tester backed by Jackson. Usually instantiated via * AssertJ based JSON tester backed by Jackson. Usually instantiated via
* {@link #initFields(Object, ObjectMapper)}, for example: <pre class="code"> * {@link #initFields(Object, JsonMapper)}, for example: <pre class="code">
* public class ExampleObjectJsonTests { * public class ExampleObjectJsonTests {
* *
* private JacksonTester&lt;ExampleObject&gt; json; * private JacksonTester&lt;ExampleObject&gt; json;
* *
* &#064;Before * &#064;Before
* public void setup() { * public void setup() {
* ObjectMapper objectMapper = new ObjectMapper(); * JsonMapper jsonMapper = new JsonMapper();
* JacksonTester.initFields(this, objectMapper); * JacksonTester.initFields(this, objectMapper);
* } * }
* *
@ -65,42 +78,44 @@ import org.springframework.util.Assert;
*/ */
public class JacksonTester<T> extends AbstractJsonMarshalTester<T> { public class JacksonTester<T> extends AbstractJsonMarshalTester<T> {
private final ObjectMapper objectMapper; private final JsonMapper jsonMapper;
private @Nullable Class<?> view; private @Nullable Class<?> view;
/** /**
* Create a new {@link JacksonTester} instance. * Create a new {@link JacksonTester} instance.
* @param objectMapper the Jackson object mapper * @param jsonMapper the Jackson JSON mapper
* @since 4.0.0
*/ */
protected JacksonTester(ObjectMapper objectMapper) { protected JacksonTester(JsonMapper jsonMapper) {
Assert.notNull(objectMapper, "'objectMapper' must not be null"); Assert.notNull(jsonMapper, "'objectMapper' must not be null");
this.objectMapper = objectMapper; this.jsonMapper = jsonMapper;
} }
/** /**
* Create a new {@link JacksonTester} instance. * Create a new {@link JacksonTester} instance.
* @param resourceLoadClass the source class used to load resources * @param resourceLoadClass the source class used to load resources
* @param type the type under test * @param type the type under test
* @param objectMapper the Jackson object mapper * @param jsonMapper the Jackson JSON mapper
* @since 4.0.0
*/ */
public JacksonTester(Class<?> resourceLoadClass, ResolvableType type, ObjectMapper objectMapper) { public JacksonTester(Class<?> resourceLoadClass, ResolvableType type, JsonMapper jsonMapper) {
this(resourceLoadClass, type, objectMapper, null); this(resourceLoadClass, type, jsonMapper, null);
} }
public JacksonTester(Class<?> resourceLoadClass, ResolvableType type, ObjectMapper objectMapper, public JacksonTester(Class<?> resourceLoadClass, ResolvableType type, JsonMapper jsonMapper,
@Nullable Class<?> view) { @Nullable Class<?> view) {
super(resourceLoadClass, type); super(resourceLoadClass, type);
Assert.notNull(objectMapper, "'objectMapper' must not be null"); Assert.notNull(jsonMapper, "'jsonMapper' must not be null");
this.objectMapper = objectMapper; this.jsonMapper = jsonMapper;
this.view = view; this.view = view;
} }
@Override @Override
protected JsonContent<T> getJsonContent(String json) { protected JsonContent<T> getJsonContent(String json) {
Configuration configuration = Configuration.builder() Configuration configuration = Configuration.builder()
.jsonProvider(new JacksonJsonProvider(this.objectMapper)) .jsonProvider(new JacksonJsonProvider(this.jsonMapper))
.mappingProvider(new JacksonMappingProvider(this.objectMapper)) .mappingProvider(new JacksonMappingProvider(this.jsonMapper))
.build(); .build();
Class<?> resourceLoadClass = getResourceLoadClass(); Class<?> resourceLoadClass = getResourceLoadClass();
Assert.state(resourceLoadClass != null, "'resourceLoadClass' must not be null"); Assert.state(resourceLoadClass != null, "'resourceLoadClass' must not be null");
@ -118,7 +133,7 @@ public class JacksonTester<T> extends AbstractJsonMarshalTester<T> {
} }
private ObjectReader getObjectReader(ResolvableType type) { private ObjectReader getObjectReader(ResolvableType type) {
ObjectReader objectReader = this.objectMapper.readerFor(getType(type)); ObjectReader objectReader = this.jsonMapper.readerFor(getType(type));
if (this.view != null) { if (this.view != null) {
return objectReader.withView(this.view); return objectReader.withView(this.view);
} }
@ -131,7 +146,7 @@ public class JacksonTester<T> extends AbstractJsonMarshalTester<T> {
} }
private ObjectWriter getObjectWriter(ResolvableType type) { private ObjectWriter getObjectWriter(ResolvableType type) {
ObjectWriter objectWriter = this.objectMapper.writerFor(getType(type)); ObjectWriter objectWriter = this.jsonMapper.writerFor(getType(type));
if (this.view != null) { if (this.view != null) {
return objectWriter.withView(this.view); return objectWriter.withView(this.view);
} }
@ -139,29 +154,31 @@ public class JacksonTester<T> extends AbstractJsonMarshalTester<T> {
} }
private JavaType getType(ResolvableType type) { private JavaType getType(ResolvableType type) {
return this.objectMapper.constructType(type.getType()); return this.jsonMapper.constructType(type.getType());
} }
/** /**
* Utility method to initialize {@link JacksonTester} fields. See {@link JacksonTester * Utility method to initialize {@link JacksonTester} fields. See {@link JacksonTester
* class-level documentation} for example usage. * class-level documentation} for example usage.
* @param testInstance the test instance * @param testInstance the test instance
* @param objectMapper the object mapper * @param jsonMapper the JSON mapper
* @see #initFields(Object, ObjectMapper) * @since 4.0.0
* @see #initFields(Object, JsonMapper)
*/ */
public static void initFields(Object testInstance, ObjectMapper objectMapper) { public static void initFields(Object testInstance, JsonMapper jsonMapper) {
new JacksonFieldInitializer().initFields(testInstance, objectMapper); new JacksonFieldInitializer().initFields(testInstance, jsonMapper);
} }
/** /**
* Utility method to initialize {@link JacksonTester} fields. See {@link JacksonTester * Utility method to initialize {@link JacksonTester} fields. See {@link JacksonTester
* class-level documentation} for example usage. * class-level documentation} for example usage.
* @param testInstance the test instance * @param testInstance the test instance
* @param objectMapperFactory a factory to create the object mapper * @param jsonMapperFactory a factory to create the JSON mapper
* @see #initFields(Object, ObjectMapper) * @since 4.0.0
* @see #initFields(Object, JsonMapper)
*/ */
public static void initFields(Object testInstance, ObjectFactory<ObjectMapper> objectMapperFactory) { public static void initFields(Object testInstance, ObjectFactory<JsonMapper> jsonMapperFactory) {
new JacksonFieldInitializer().initFields(testInstance, objectMapperFactory); new JacksonFieldInitializer().initFields(testInstance, jsonMapperFactory);
} }
/** /**
@ -175,13 +192,13 @@ public class JacksonTester<T> extends AbstractJsonMarshalTester<T> {
ResolvableType type = getType(); ResolvableType type = getType();
Assert.state(resourceLoadClass != null, "'resourceLoadClass' must not be null"); Assert.state(resourceLoadClass != null, "'resourceLoadClass' must not be null");
Assert.state(type != null, "'type' must not be null"); Assert.state(type != null, "'type' must not be null");
return new JacksonTester<>(resourceLoadClass, type, this.objectMapper, view); return new JacksonTester<>(resourceLoadClass, type, this.jsonMapper, view);
} }
/** /**
* {@link FieldInitializer} for Jackson. * {@link FieldInitializer} for Jackson.
*/ */
private static class JacksonFieldInitializer extends FieldInitializer<ObjectMapper> { private static class JacksonFieldInitializer extends FieldInitializer<JsonMapper> {
protected JacksonFieldInitializer() { protected JacksonFieldInitializer() {
super(JacksonTester.class); super(JacksonTester.class);
@ -189,10 +206,115 @@ public class JacksonTester<T> extends AbstractJsonMarshalTester<T> {
@Override @Override
protected AbstractJsonMarshalTester<Object> createTester(Class<?> resourceLoadClass, ResolvableType type, protected AbstractJsonMarshalTester<Object> createTester(Class<?> resourceLoadClass, ResolvableType type,
ObjectMapper marshaller) { JsonMapper marshaller) {
return new JacksonTester<>(resourceLoadClass, type, marshaller); return new JacksonTester<>(resourceLoadClass, type, marshaller);
} }
} }
private static final class JacksonJsonProvider extends AbstractJsonProvider {
private final ObjectMapper objectMapper;
private final ObjectReader objectReader;
private JacksonJsonProvider(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
this.objectReader = objectMapper.reader().forType(Object.class);
}
@Override
public Object parse(String json) throws InvalidJsonException {
try {
return this.objectReader.readValue(json);
}
catch (JacksonException ex) {
throw new InvalidJsonException(ex, json);
}
}
@Override
public Object parse(byte[] json) throws InvalidJsonException {
try {
return this.objectReader.readValue(json);
}
catch (JacksonException ex) {
throw new InvalidJsonException(ex, new String(json, StandardCharsets.UTF_8));
}
}
@Override
public Object parse(InputStream jsonStream, String charset) throws InvalidJsonException {
try {
return this.objectReader.readValue(new InputStreamReader(jsonStream, charset));
}
catch (UnsupportedEncodingException | JacksonException ex) {
throw new InvalidJsonException(ex);
}
}
@Override
public String toJson(Object obj) {
StringWriter writer = new StringWriter();
try (JsonGenerator generator = this.objectMapper.createGenerator(writer)) {
this.objectMapper.writeValue(generator, obj);
}
catch (JacksonException ex) {
throw new InvalidJsonException(ex);
}
return writer.toString();
}
@Override
public List<Object> createArray() {
return new LinkedList<>();
}
@Override
public Object createMap() {
return new LinkedHashMap<String, Object>();
}
}
private static final class JacksonMappingProvider implements MappingProvider {
private final ObjectMapper objectMapper;
private JacksonMappingProvider(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@Override
public <T> @Nullable T map(Object source, Class<T> targetType, Configuration configuration) {
if (source == null) {
return null;
}
try {
return this.objectMapper.convertValue(source, targetType);
}
catch (Exception ex) {
throw new MappingException(ex);
}
}
@Override
@SuppressWarnings("unchecked")
public <T> @Nullable T map(Object source, final TypeRef<T> targetType, Configuration configuration) {
if (source == null) {
return null;
}
JavaType type = this.objectMapper.getTypeFactory().constructType(targetType.getType());
try {
return (T) this.objectMapper.convertValue(source, type);
}
catch (Exception ex) {
throw new MappingException(ex);
}
}
}
} }

View File

@ -22,11 +22,10 @@ import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;
import org.assertj.core.api.InstanceOfAssertFactories; import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import tools.jackson.databind.MapperFeature;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.ByteArrayResource;
@ -55,14 +54,14 @@ class JacksonTesterIntegrationTests {
@Test @Test
void typicalTest() throws Exception { void typicalTest() throws Exception {
JacksonTester.initFields(this, new ObjectMapper()); JacksonTester.initFields(this, new JsonMapper());
String example = JSON; String example = JSON;
assertThat(this.simpleJson.parse(example).getObject().getName()).isEqualTo("Spring"); assertThat(this.simpleJson.parse(example).getObject().getName()).isEqualTo("Spring");
} }
@Test @Test
void typicalListTest() throws Exception { void typicalListTest() throws Exception {
JacksonTester.initFields(this, new ObjectMapper()); JacksonTester.initFields(this, new JsonMapper());
String example = "[" + JSON + "]"; String example = "[" + JSON + "]";
assertThat(this.listJson.parse(example)).asInstanceOf(InstanceOfAssertFactories.LIST).hasSize(1); assertThat(this.listJson.parse(example)).asInstanceOf(InstanceOfAssertFactories.LIST).hasSize(1);
assertThat(this.listJson.parse(example).getObject().get(0).getName()).isEqualTo("Spring"); assertThat(this.listJson.parse(example).getObject().get(0).getName()).isEqualTo("Spring");
@ -70,7 +69,7 @@ class JacksonTesterIntegrationTests {
@Test @Test
void typicalMapTest() throws Exception { void typicalMapTest() throws Exception {
JacksonTester.initFields(this, new ObjectMapper()); JacksonTester.initFields(this, new JsonMapper());
Map<String, Integer> map = new LinkedHashMap<>(); Map<String, Integer> map = new LinkedHashMap<>();
map.put("a", 1); map.put("a", 1);
map.put("b", 2); map.put("b", 2);
@ -79,7 +78,7 @@ class JacksonTesterIntegrationTests {
@Test @Test
void stringLiteral() throws Exception { void stringLiteral() throws Exception {
JacksonTester.initFields(this, new ObjectMapper()); JacksonTester.initFields(this, new JsonMapper());
String stringWithSpecialCharacters = "myString"; String stringWithSpecialCharacters = "myString";
assertThat(this.stringJson.write(stringWithSpecialCharacters)).extractingJsonPathStringValue("@") assertThat(this.stringJson.write(stringWithSpecialCharacters)).extractingJsonPathStringValue("@")
.isEqualTo(stringWithSpecialCharacters); .isEqualTo(stringWithSpecialCharacters);
@ -87,7 +86,7 @@ class JacksonTesterIntegrationTests {
@Test @Test
void parseSpecialCharactersTest() throws Exception { void parseSpecialCharactersTest() throws Exception {
JacksonTester.initFields(this, new ObjectMapper()); JacksonTester.initFields(this, new JsonMapper());
// Confirms that the handling of special characters is symmetrical between // Confirms that the handling of special characters is symmetrical between
// the serialization (through the JacksonTester) and the parsing (through // the serialization (through the JacksonTester) and the parsing (through
// json-path). By default json-path uses SimpleJson as its parser, which has a // json-path). By default json-path uses SimpleJson as its parser, which has a

View File

@ -18,8 +18,8 @@ package org.springframework.boot.test.json;
import java.util.List; import java.util.List;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.core.ResolvableType; import org.springframework.core.ResolvableType;
@ -35,14 +35,14 @@ class JacksonTesterTests extends AbstractJsonMarshalTesterTests {
@Test @Test
void initFieldsWhenTestIsNullShouldThrowException() { void initFieldsWhenTestIsNullShouldThrowException() {
assertThatIllegalArgumentException().isThrownBy(() -> JacksonTester.initFields(null, new ObjectMapper())) assertThatIllegalArgumentException().isThrownBy(() -> JacksonTester.initFields(null, new JsonMapper()))
.withMessageContaining("'testInstance' must not be null"); .withMessageContaining("'testInstance' must not be null");
} }
@Test @Test
void initFieldsWhenMarshallerIsNullShouldThrowException() { void initFieldsWhenMarshallerIsNullShouldThrowException() {
assertThatIllegalArgumentException() assertThatIllegalArgumentException()
.isThrownBy(() -> JacksonTester.initFields(new InitFieldsTestClass(), (ObjectMapper) null)) .isThrownBy(() -> JacksonTester.initFields(new InitFieldsTestClass(), (JsonMapper) null))
.withMessageContaining("'marshaller' must not be null"); .withMessageContaining("'marshaller' must not be null");
} }
@ -51,7 +51,7 @@ class JacksonTesterTests extends AbstractJsonMarshalTesterTests {
InitFieldsTestClass test = new InitFieldsTestClass(); InitFieldsTestClass test = new InitFieldsTestClass();
assertThat(test.test).isNull(); assertThat(test.test).isNull();
assertThat(test.base).isNull(); assertThat(test.base).isNull();
JacksonTester.initFields(test, new ObjectMapper()); JacksonTester.initFields(test, new JsonMapper());
assertThat(test.test).isNotNull(); assertThat(test.test).isNotNull();
assertThat(test.base).isNotNull(); assertThat(test.base).isNotNull();
assertThat(test.test.getType().resolve()).isEqualTo(List.class); assertThat(test.test.getType().resolve()).isEqualTo(List.class);
@ -60,7 +60,7 @@ class JacksonTesterTests extends AbstractJsonMarshalTesterTests {
@Override @Override
protected AbstractJsonMarshalTester<Object> createTester(Class<?> resourceLoadClass, ResolvableType type) { protected AbstractJsonMarshalTester<Object> createTester(Class<?> resourceLoadClass, ResolvableType type) {
return new JacksonTester<>(resourceLoadClass, type, new ObjectMapper()); return new JacksonTester<>(resourceLoadClass, type, new JsonMapper());
} }
abstract static class InitFieldsBaseClass { abstract static class InitFieldsBaseClass {
@ -68,7 +68,7 @@ class JacksonTesterTests extends AbstractJsonMarshalTesterTests {
public JacksonTester<ExampleObject> base; public JacksonTester<ExampleObject> base;
public JacksonTester<ExampleObject> baseSet = new JacksonTester<>(InitFieldsBaseClass.class, public JacksonTester<ExampleObject> baseSet = new JacksonTester<>(InitFieldsBaseClass.class,
ResolvableType.forClass(ExampleObject.class), new ObjectMapper()); ResolvableType.forClass(ExampleObject.class), new JsonMapper());
} }
@ -77,7 +77,7 @@ class JacksonTesterTests extends AbstractJsonMarshalTesterTests {
public JacksonTester<List<ExampleObject>> test; public JacksonTester<List<ExampleObject>> test;
public JacksonTester<ExampleObject> testSet = new JacksonTester<>(InitFieldsBaseClass.class, public JacksonTester<ExampleObject> testSet = new JacksonTester<>(InitFieldsBaseClass.class,
ResolvableType.forClass(ExampleObject.class), new ObjectMapper()); ResolvableType.forClass(ExampleObject.class), new JsonMapper());
} }

View File

@ -33,7 +33,6 @@ dependencies {
api("org.springframework:spring-context") api("org.springframework:spring-context")
optional("ch.qos.logback:logback-classic") optional("ch.qos.logback:logback-classic")
optional("com.fasterxml.jackson.core:jackson-databind")
optional("com.google.code.gson:gson") optional("com.google.code.gson:gson")
optional("io.projectreactor:reactor-core") optional("io.projectreactor:reactor-core")
optional("jakarta.servlet:jakarta.servlet-api") optional("jakarta.servlet:jakarta.servlet-api")
@ -49,6 +48,7 @@ dependencies {
optional("org.springframework:spring-test") optional("org.springframework:spring-test")
optional("org.springframework:spring-web") optional("org.springframework:spring-web")
optional("org.yaml:snakeyaml") optional("org.yaml:snakeyaml")
optional("tools.jackson.core:jackson-databind")
testFixturesCompileOnly(project(":test-support:spring-boot-test-support")) testFixturesCompileOnly(project(":test-support:spring-boot-test-support"))

View File

@ -19,9 +19,9 @@ package org.springframework.boot.json;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.ObjectMapper;
/** /**
* Thin wrapper to adapt Jackson 2 {@link ObjectMapper} to {@link JsonParser}. * Thin wrapper to adapt Jackson 2 {@link ObjectMapper} to {@link JsonParser}.

View File

@ -1,56 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.json;
import com.fasterxml.jackson.databind.ser.std.ClassSerializer;
import com.fasterxml.jackson.databind.ser.std.FileSerializer;
import com.fasterxml.jackson.databind.ser.std.StdJdkSerializers.AtomicBooleanSerializer;
import com.fasterxml.jackson.databind.ser.std.StdJdkSerializers.AtomicIntegerSerializer;
import com.fasterxml.jackson.databind.ser.std.StdJdkSerializers.AtomicLongSerializer;
import com.fasterxml.jackson.databind.ser.std.TokenBufferSerializer;
import org.jspecify.annotations.Nullable;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.ReflectionHints;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.TypeHint;
import org.springframework.aot.hint.TypeReference;
import org.springframework.util.ClassUtils;
/**
* {@link RuntimeHintsRegistrar} implementation for Jackson.
*
* @author Moritz Halbritter
*/
class JacksonRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
if (!ClassUtils.isPresent("com.fasterxml.jackson.databind.ser.BasicSerializerFactory", classLoader)) {
return;
}
registerSerializers(hints.reflection());
}
private void registerSerializers(ReflectionHints hints) {
hints.registerTypes(TypeReference.listOf(AtomicBooleanSerializer.class, AtomicIntegerSerializer.class,
AtomicLongSerializer.class, FileSerializer.class, ClassSerializer.class, TokenBufferSerializer.class),
TypeHint.builtWith(MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS));
}
}

View File

@ -35,7 +35,7 @@ public abstract class JsonParserFactory {
* @return a {@link JsonParser} * @return a {@link JsonParser}
*/ */
public static JsonParser getJsonParser() { public static JsonParser getJsonParser() {
if (ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", null)) { if (ClassUtils.isPresent("tools.jackson.databind.ObjectMapper", null)) {
return new JacksonJsonParser(); return new JacksonJsonParser();
} }
if (ClassUtils.isPresent("com.google.gson.Gson", null)) { if (ClassUtils.isPresent("com.google.gson.Gson", null)) {

View File

@ -5,7 +5,6 @@ org.springframework.boot.WebApplicationType$WebApplicationTypeRuntimeHints,\
org.springframework.boot.context.config.ConfigDataLocationRuntimeHints,\ org.springframework.boot.context.config.ConfigDataLocationRuntimeHints,\
org.springframework.boot.context.config.ConfigDataPropertiesRuntimeHints,\ org.springframework.boot.context.config.ConfigDataPropertiesRuntimeHints,\
org.springframework.boot.env.PropertySourceRuntimeHints,\ org.springframework.boot.env.PropertySourceRuntimeHints,\
org.springframework.boot.json.JacksonRuntimeHints,\
org.springframework.boot.logging.java.JavaLoggingSystemRuntimeHints,\ org.springframework.boot.logging.java.JavaLoggingSystemRuntimeHints,\
org.springframework.boot.logging.logback.LogbackRuntimeHints,\ org.springframework.boot.logging.logback.LogbackRuntimeHints,\
org.springframework.boot.logging.structured.ElasticCommonSchemaProperties$ElasticCommonSchemaPropertiesRuntimeHints,\ org.springframework.boot.logging.structured.ElasticCommonSchemaProperties$ElasticCommonSchemaPropertiesRuntimeHints,\

View File

@ -16,12 +16,10 @@
package org.springframework.boot.json; package org.springframework.boot.json;
import java.io.IOException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.ObjectMapper;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
@ -43,7 +41,7 @@ class JacksonJsonParserTests extends AbstractJsonParserTests {
@Test @Test
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
void instanceWithSpecificObjectMapper() throws IOException { void instanceWithSpecificObjectMapper() {
ObjectMapper objectMapper = spy(new ObjectMapper()); ObjectMapper objectMapper = spy(new ObjectMapper());
new JacksonJsonParser(objectMapper).parseMap("{}"); new JacksonJsonParser(objectMapper).parseMap("{}");
then(objectMapper).should().readValue(eq("{}"), any(TypeReference.class)); then(objectMapper).should().readValue(eq("{}"), any(TypeReference.class));

View File

@ -1,64 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.json;
import java.util.Set;
import java.util.stream.Stream;
import com.fasterxml.jackson.databind.ser.std.ClassSerializer;
import com.fasterxml.jackson.databind.ser.std.FileSerializer;
import com.fasterxml.jackson.databind.ser.std.StdJdkSerializers.AtomicBooleanSerializer;
import com.fasterxml.jackson.databind.ser.std.StdJdkSerializers.AtomicIntegerSerializer;
import com.fasterxml.jackson.databind.ser.std.StdJdkSerializers.AtomicLongSerializer;
import com.fasterxml.jackson.databind.ser.std.TokenBufferSerializer;
import org.junit.jupiter.api.Test;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.ReflectionHints;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.TypeHint;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link JacksonRuntimeHints}.
*
* @author Moritz Halbritter
*/
class JacksonRuntimeHintsTests {
@Test
void shouldRegisterSerializerConstructors() {
ReflectionHints hints = registerHints();
Stream
.of(AtomicBooleanSerializer.class, AtomicIntegerSerializer.class, AtomicLongSerializer.class,
FileSerializer.class, ClassSerializer.class, TokenBufferSerializer.class)
.forEach((serializer) -> {
TypeHint typeHint = hints.getTypeHint(serializer);
assertThat(typeHint).withFailMessage(() -> "No hints found for serializer " + serializer).isNotNull();
Set<MemberCategory> memberCategories = typeHint.getMemberCategories();
assertThat(memberCategories).containsExactly(MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS);
});
}
private ReflectionHints registerHints() {
RuntimeHints hints = new RuntimeHints();
new JacksonRuntimeHints().registerHints(hints, getClass().getClassLoader());
return hints.reflection();
}
}

View File

@ -20,16 +20,14 @@ import java.time.Instant;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.impl.MutableLogEvent; import org.apache.logging.log4j.core.impl.MutableLogEvent;
import org.apache.logging.log4j.message.SimpleMessage; import org.apache.logging.log4j.message.SimpleMessage;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.ObjectMapper;
import org.springframework.boot.logging.structured.MockStructuredLoggingJsonMembersCustomizerBuilder; import org.springframework.boot.logging.structured.MockStructuredLoggingJsonMembersCustomizerBuilder;
import org.springframework.boot.logging.structured.StructuredLoggingJsonMembersCustomizer; import org.springframework.boot.logging.structured.StructuredLoggingJsonMembersCustomizer;
@ -79,14 +77,8 @@ abstract class AbstractStructuredLoggingTests {
} }
protected Map<String, Object> deserialize(String json) { protected Map<String, Object> deserialize(String json) {
try { return OBJECT_MAPPER.readValue(json, new TypeReference<>() {
return OBJECT_MAPPER.readValue(json, new TypeReference<>() { });
});
}
catch (JsonProcessingException ex) {
Assertions.fail("Failed to deserialize JSON: " + json, ex);
return null;
}
} }
} }

View File

@ -34,7 +34,6 @@ import java.util.logging.Handler;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Stream; import java.util.stream.Stream;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
@ -333,15 +332,16 @@ class Log4J2LoggingSystemTests extends AbstractLoggingSystemTests {
// No classes, only XML // No classes, only XML
Arguments.of(Collections.emptyList(), List.of(".xml")), Arguments.of(Collections.emptyList(), List.of(".xml")),
// Log4j Core 2 // Log4j Core 2
Arguments.of(List.of(JsonConfigurationFactory.class.getName(), ObjectMapper.class.getName()), Arguments.of(List.of(JsonConfigurationFactory.class.getName(),
List.of(".json", ".jsn", ".xml")), "com.fasterxml.jackson.databind.ObjectMapper"), List.of(".json", ".jsn", ".xml")),
Arguments.of(List.of(PropertiesConfigurationFactory.class.getName(), Arguments.of(List.of(PropertiesConfigurationFactory.class.getName(),
PropertiesConfigurationBuilder.class.getName()), List.of(".properties", ".xml")), PropertiesConfigurationBuilder.class.getName()), List.of(".properties", ".xml")),
Arguments.of(List.of(YamlConfigurationFactory.class.getName(), Arguments.of(List.of(YamlConfigurationFactory.class.getName(),
"com.fasterxml.jackson.dataformat.yaml.YAMLMapper"), List.of(".yaml", ".yml", ".xml")), "com.fasterxml.jackson.dataformat.yaml.YAMLMapper"), List.of(".yaml", ".yml", ".xml")),
Arguments.of(List.of(JsonConfigurationFactory.class.getName(), ObjectMapper.class.getName(), Arguments.of(List.of(JsonConfigurationFactory.class.getName(),
PropertiesConfigurationFactory.class.getName(), PropertiesConfigurationBuilder.class.getName(), "com.fasterxml.jackson.databind.ObjectMapper", PropertiesConfigurationFactory.class.getName(),
YamlConfigurationFactory.class.getName(), "com.fasterxml.jackson.dataformat.yaml.YAMLMapper"), PropertiesConfigurationBuilder.class.getName(), YamlConfigurationFactory.class.getName(),
"com.fasterxml.jackson.dataformat.yaml.YAMLMapper"),
List.of(".properties", ".yaml", ".yml", ".json", ".jsn", ".xml")), List.of(".properties", ".yaml", ".yml", ".json", ".jsn", ".xml")),
// Log4j Core 3 // Log4j Core 3
Arguments.of(List.of(JsonConfigurationFactory.class.getName(), Arguments.of(List.of(JsonConfigurationFactory.class.getName(),

View File

@ -26,10 +26,6 @@ import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.pattern.ThrowableProxyConverter; import ch.qos.logback.classic.pattern.ThrowableProxyConverter;
import ch.qos.logback.classic.spi.LoggingEvent; import ch.qos.logback.classic.spi.LoggingEvent;
import ch.qos.logback.classic.spi.ThrowableProxy; import ch.qos.logback.classic.spi.ThrowableProxy;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
@ -38,6 +34,8 @@ import org.mockito.junit.jupiter.MockitoExtension;
import org.slf4j.Marker; import org.slf4j.Marker;
import org.slf4j.event.KeyValuePair; import org.slf4j.event.KeyValuePair;
import org.slf4j.helpers.BasicMarkerFactory; import org.slf4j.helpers.BasicMarkerFactory;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.ObjectMapper;
import org.springframework.boot.logging.structured.MockStructuredLoggingJsonMembersCustomizerBuilder; import org.springframework.boot.logging.structured.MockStructuredLoggingJsonMembersCustomizerBuilder;
import org.springframework.boot.logging.structured.StructuredLoggingJsonMembersCustomizer; import org.springframework.boot.logging.structured.StructuredLoggingJsonMembersCustomizer;
@ -122,14 +120,8 @@ abstract class AbstractStructuredLoggingTests {
} }
protected Map<String, Object> deserialize(String json) { protected Map<String, Object> deserialize(String json) {
try { return OBJECT_MAPPER.readValue(json, new TypeReference<>() {
return OBJECT_MAPPER.readValue(json, new TypeReference<>() { });
});
}
catch (JsonProcessingException ex) {
Assertions.fail("Failed to deserialize JSON: " + json, ex);
return null;
}
} }
} }

View File

@ -18,9 +18,8 @@ package org.springframework.boot.web.error;
import java.util.List; import java.util.List;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import tools.jackson.databind.ObjectMapper;
import org.springframework.context.MessageSourceResolvable; import org.springframework.context.MessageSourceResolvable;
import org.springframework.context.support.DefaultMessageSourceResolvable; import org.springframework.context.support.DefaultMessageSourceResolvable;
@ -46,7 +45,7 @@ class ErrorTests {
} }
@Test @Test
void errorCauseDoesNotAppearInJson() throws JsonProcessingException { void errorCauseDoesNotAppearInJson() {
String json = new ObjectMapper() String json = new ObjectMapper()
.writeValueAsString(Error.wrapIfNecessary(List.of(new CustomMessageSourceResolvable("code")))); .writeValueAsString(Error.wrapIfNecessary(List.of(new CustomMessageSourceResolvable("code"))));
assertThat(json).doesNotContain("some detail"); assertThat(json).doesNotContain("some detail");

View File

@ -16,15 +16,14 @@
package org.springframework.boot.actuate.docs; package org.springframework.boot.actuate.docs;
import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Stream; import java.util.stream.Stream;
import com.fasterxml.jackson.databind.ObjectMapper; import tools.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializationFeature; import tools.jackson.databind.json.JsonMapper;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.BeanPostProcessor;
@ -32,7 +31,7 @@ import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfi
import org.springframework.boot.actuate.autoconfigure.endpoint.jackson.JacksonEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.jackson.JacksonEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
import org.springframework.boot.actuate.docs.AbstractEndpointDocumentationTests.BaseDocumentationConfiguration; import org.springframework.boot.actuate.docs.AbstractEndpointDocumentationTests.BaseDocumentationConfiguration;
import org.springframework.boot.actuate.endpoint.jackson.EndpointObjectMapper; import org.springframework.boot.actuate.endpoint.jackson.EndpointJsonMapper;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.http.converter.autoconfigure.HttpMessageConvertersAutoConfiguration; import org.springframework.boot.http.converter.autoconfigure.HttpMessageConvertersAutoConfiguration;
@ -77,29 +76,24 @@ public abstract class AbstractEndpointDocumentationTests {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
protected <T> OperationPreprocessor limit(Predicate<T> filter, String... keys) { protected <T> OperationPreprocessor limit(Predicate<T> filter, String... keys) {
return new ContentModifyingOperationPreprocessor((content, mediaType) -> { return new ContentModifyingOperationPreprocessor((content, mediaType) -> {
ObjectMapper objectMapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT); JsonMapper objectMapper = JsonMapper.builder().enable(SerializationFeature.INDENT_OUTPUT).build();
try { Map<String, Object> payload = objectMapper.readValue(content, Map.class);
Map<String, Object> payload = objectMapper.readValue(content, Map.class); Object target = payload;
Object target = payload; Map<Object, Object> parent = null;
Map<Object, Object> parent = null; for (String key : keys) {
for (String key : keys) { if (!(target instanceof Map)) {
if (!(target instanceof Map)) { throw new IllegalStateException();
throw new IllegalStateException();
}
parent = (Map<Object, Object>) target;
target = parent.get(key);
} }
if (target instanceof Map) { parent = (Map<Object, Object>) target;
parent.put(keys[keys.length - 1], select((Map<String, Object>) target, filter)); target = parent.get(key);
}
else {
parent.put(keys[keys.length - 1], select((List<Object>) target, filter));
}
return objectMapper.writeValueAsBytes(payload);
} }
catch (IOException ex) { if (target instanceof Map) {
throw new IllegalStateException(ex); parent.put(keys[keys.length - 1], select((Map<String, Object>) target, filter));
} }
else {
parent.put(keys[keys.length - 1], select((List<Object>) target, filter));
}
return objectMapper.writeValueAsBytes(payload);
}); });
} }
@ -135,14 +129,16 @@ public abstract class AbstractEndpointDocumentationTests {
static class BaseDocumentationConfiguration { static class BaseDocumentationConfiguration {
@Bean @Bean
static BeanPostProcessor endpointObjectMapperBeanPostProcessor() { static BeanPostProcessor endpointJsonMapperBeanPostProcessor() {
return new BeanPostProcessor() { return new BeanPostProcessor() {
@Override @Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof EndpointObjectMapper) { if (bean instanceof EndpointJsonMapper) {
return (EndpointObjectMapper) () -> ((EndpointObjectMapper) bean).get() return (EndpointJsonMapper) () -> ((EndpointJsonMapper) bean).get()
.enable(SerializationFeature.INDENT_OUTPUT); .rebuild()
.enable(SerializationFeature.INDENT_OUTPUT)
.build();
} }
return bean; return bean;
} }

View File

@ -16,7 +16,6 @@
package org.springframework.boot.actuate.docs.env; package org.springframework.boot.actuate.docs.env;
import java.io.IOException;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -24,9 +23,10 @@ import java.util.Set;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.SerializationFeature;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.boot.actuate.docs.MockMvcEndpointDocumentationTests; import org.springframework.boot.actuate.docs.MockMvcEndpointDocumentationTests;
import org.springframework.boot.actuate.endpoint.Show; import org.springframework.boot.actuate.endpoint.Show;
@ -116,24 +116,19 @@ class EnvironmentEndpointDocumentationTests extends MockMvcEndpointDocumentation
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private byte[] filterProperties(byte[] content, MediaType mediaType) { private byte[] filterProperties(byte[] content, MediaType mediaType) {
ObjectMapper objectMapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT); ObjectMapper objectMapper = JsonMapper.builder().enable(SerializationFeature.INDENT_OUTPUT).build();
try { Map<String, Object> payload = objectMapper.readValue(content, Map.class);
Map<String, Object> payload = objectMapper.readValue(content, Map.class); List<Map<String, Object>> propertySources = (List<Map<String, Object>>) payload.get("propertySources");
List<Map<String, Object>> propertySources = (List<Map<String, Object>>) payload.get("propertySources"); for (Map<String, Object> propertySource : propertySources) {
for (Map<String, Object> propertySource : propertySources) { Map<String, String> properties = (Map<String, String>) propertySource.get("properties");
Map<String, String> properties = (Map<String, String>) propertySource.get("properties"); Set<String> filteredKeys = properties.keySet()
Set<String> filteredKeys = properties.keySet() .stream()
.stream() .filter(this::retainKey)
.filter(this::retainKey) .limit(3)
.limit(3) .collect(Collectors.toSet());
.collect(Collectors.toSet()); properties.keySet().retainAll(filteredKeys);
properties.keySet().retainAll(filteredKeys);
}
return objectMapper.writeValueAsBytes(payload);
}
catch (IOException ex) {
throw new IllegalStateException(ex);
} }
return objectMapper.writeValueAsBytes(payload);
} }
private boolean retainKey(String key) { private boolean retainKey(String key) {

View File

@ -34,7 +34,6 @@ import org.springframework.restdocs.payload.FieldDescriptor;
import org.springframework.session.FindByIndexNameSessionRepository; import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.MapSession; import org.springframework.session.MapSession;
import org.springframework.session.Session; import org.springframework.session.Session;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.context.bean.override.mockito.MockitoBean;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -51,7 +50,6 @@ import static org.springframework.restdocs.request.RequestDocumentation.queryPar
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
@TestPropertySource(properties = "spring.jackson.serialization.write-dates-as-timestamps=false")
class SessionsEndpointDocumentationTests extends MockMvcEndpointDocumentationTests { class SessionsEndpointDocumentationTests extends MockMvcEndpointDocumentationTests {
private static final Session sessionOne = createSession(Instant.now().minusSeconds(60 * 60 * 12), private static final Session sessionOne = createSession(Instant.now().minusSeconds(60 * 60 * 12),

View File

@ -634,8 +634,8 @@
* xref:how-to:security.adoc#howto.security.switch-off-spring-boot-configuration[#howto.security.switch-off-spring-boot-configuration] * xref:how-to:security.adoc#howto.security.switch-off-spring-boot-configuration[#howto.security.switch-off-spring-boot-configuration]
* xref:how-to:security.adoc#howto.security[#howto-security] * xref:how-to:security.adoc#howto.security[#howto-security]
* xref:how-to:security.adoc#howto.security[#howto.security] * xref:how-to:security.adoc#howto.security[#howto.security]
* xref:how-to:spring-mvc.adoc#howto.spring-mvc.customize-jackson-objectmapper[#howto-customize-the-jackson-objectmapper] * xref:how-to:spring-mvc.adoc#howto.spring-mvc.customize-jackson-jsonmapper[#howto-customize-the-jackson-objectmapper]
* xref:how-to:spring-mvc.adoc#howto.spring-mvc.customize-jackson-objectmapper[#howto.spring-mvc.customize-jackson-objectmapper] * xref:how-to:spring-mvc.adoc#howto.spring-mvc.customize-jackson-jsonmapper[#howto.spring-mvc.customize-jackson-objectmapper]
* xref:how-to:spring-mvc.adoc#howto.spring-mvc.customize-responsebody-rendering[#howto-customize-the-responsebody-rendering] * xref:how-to:spring-mvc.adoc#howto.spring-mvc.customize-responsebody-rendering[#howto-customize-the-responsebody-rendering]
* xref:how-to:spring-mvc.adoc#howto.spring-mvc.customize-responsebody-rendering[#howto.spring-mvc.customize-responsebody-rendering] * xref:how-to:spring-mvc.adoc#howto.spring-mvc.customize-responsebody-rendering[#howto.spring-mvc.customize-responsebody-rendering]
* xref:how-to:spring-mvc.adoc#howto.spring-mvc.customize-view-resolvers[#howto-customize-view-resolvers] * xref:how-to:spring-mvc.adoc#howto.spring-mvc.customize-view-resolvers[#howto-customize-view-resolvers]

View File

@ -10,11 +10,11 @@ This section answers common questions about Spring MVC and Spring Boot.
[[howto.spring-mvc.write-json-rest-service]] [[howto.spring-mvc.write-json-rest-service]]
== Write a JSON REST Service == Write a JSON REST Service
Any Spring javadoc:org.springframework.web.bind.annotation.RestController[format=annotation] in a Spring Boot application should render JSON response by default as long as Jackson2 is on the classpath, as shown in the following example: Any Spring javadoc:org.springframework.web.bind.annotation.RestController[format=annotation] in a Spring Boot application should render JSON response by default as long as Jackson 3 is on the classpath, as shown in the following example:
include-code::MyController[] include-code::MyController[]
As long as `MyThing` can be serialized by Jackson2 (true for a normal POJO or Groovy object), then `http://localhost:8080/thing` serves a JSON representation of it by default. As long as `MyThing` can be serialized by Jackson 3 (true for a normal POJO or Groovy object), then `http://localhost:8080/thing` serves a JSON representation of it by default.
Note that, in a browser, you might sometimes see XML responses, because browsers tend to send accept headers that prefer XML. Note that, in a browser, you might sometimes see XML responses, because browsers tend to send accept headers that prefer XML.
@ -29,7 +29,7 @@ To use the Jackson XML renderer, add the following dependency to your project:
[source,xml] [source,xml]
---- ----
<dependency> <dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId> <groupId>tools.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId> <artifactId>jackson-dataformat-xml</artifactId>
</dependency> </dependency>
---- ----
@ -52,53 +52,41 @@ NOTE: To get the server to render XML instead of JSON, you might have to send an
[[howto.spring-mvc.customize-jackson-objectmapper]] [[howto.spring-mvc.customize-jackson-jsonmapper]]
== Customize the Jackson ObjectMapper == Customize the Jackson JsonMapper
Spring MVC (client and server side) uses javadoc:org.springframework.boot.http.converter.autoconfigure.HttpMessageConverters[] to negotiate content conversion in an HTTP exchange. You can configure the javadoc:tools.jackson.databind.JsonMapper[] by using the environment.
If Jackson is on the classpath, you already get the default converter(s) provided by javadoc:org.springframework.http.converter.json.Jackson2ObjectMapperBuilder[], an instance of which is auto-configured for you.
The javadoc:com.fasterxml.jackson.databind.ObjectMapper[] (or javadoc:com.fasterxml.jackson.dataformat.xml.XmlMapper[] for Jackson XML converter) instance (created by default) has the following customized properties:
* `MapperFeature.DEFAULT_VIEW_INCLUSION` is disabled
* `DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES` is disabled
* `SerializationFeature.WRITE_DATES_AS_TIMESTAMPS` is disabled
* `SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS` is disabled
Spring Boot also has some features to make it easier to customize this behavior.
You can configure the javadoc:com.fasterxml.jackson.databind.ObjectMapper[] and javadoc:com.fasterxml.jackson.dataformat.xml.XmlMapper[] instances by using the environment.
Jackson provides an extensive suite of on/off features that can be used to configure various aspects of its processing. Jackson provides an extensive suite of on/off features that can be used to configure various aspects of its processing.
These features are described in several enums (in Jackson) that map onto properties in the environment: These features are described in several enums (in Jackson) that map onto properties in the environment:
|=== |===
| Enum | Property | Values | Enum | Property | Values
| javadoc:com.fasterxml.jackson.databind.cfg.EnumFeature[] | javadoc:tools.jackson.databind.cfg.EnumFeature[]
| `spring.jackson.datatype.enum.<feature_name>` | `spring.jackson.datatype.enum.<feature_name>`
| `true`, `false` | `true`, `false`
| javadoc:com.fasterxml.jackson.databind.cfg.JsonNodeFeature[] | javadoc:tools.jackson.databind.cfg.JsonNodeFeature[]
| `spring.jackson.datatype.json-node.<feature_name>` | `spring.jackson.datatype.json-node.<feature_name>`
| `true`, `false` | `true`, `false`
| javadoc:com.fasterxml.jackson.databind.DeserializationFeature[] | javadoc:tools.jackson.databind.DeserializationFeature[]
| `spring.jackson.deserialization.<feature_name>` | `spring.jackson.deserialization.<feature_name>`
| `true`, `false` | `true`, `false`
| javadoc:com.fasterxml.jackson.core.JsonGenerator$Feature[] | javadoc:tools.jackson.databind.MapperFeature[]
| `spring.jackson.generator.<feature_name>`
| `true`, `false`
| javadoc:com.fasterxml.jackson.databind.MapperFeature[]
| `spring.jackson.mapper.<feature_name>` | `spring.jackson.mapper.<feature_name>`
| `true`, `false` | `true`, `false`
| javadoc:com.fasterxml.jackson.core.JsonParser$Feature[] | javadoc:tools.jackson.core.JsonReadFeature[]
| `spring.jackson.parser.<feature_name>` | `spring.jackson.read.<feature_name>`
| `true`, `false` | `true`, `false`
| javadoc:com.fasterxml.jackson.databind.SerializationFeature[] | javadoc:tools.jackson.core.JsonWriteFeature[]
| `spring.jackson.write.<feature_name>`
| `true`, `false`
| javadoc:tools.jackson.databind.SerializationFeature[]
| `spring.jackson.serialization.<feature_name>` | `spring.jackson.serialization.<feature_name>`
| `true`, `false` | `true`, `false`
@ -110,21 +98,19 @@ These features are described in several enums (in Jackson) that map onto propert
For example, to enable pretty print, set `spring.jackson.serialization.indent_output=true`. For example, to enable pretty print, set `spring.jackson.serialization.indent_output=true`.
Note that, thanks to the use of xref:reference:features/external-config.adoc#features.external-config.typesafe-configuration-properties.relaxed-binding[relaxed binding], the case of `indent_output` does not have to match the case of the corresponding enum constant, which is `INDENT_OUTPUT`. Note that, thanks to the use of xref:reference:features/external-config.adoc#features.external-config.typesafe-configuration-properties.relaxed-binding[relaxed binding], the case of `indent_output` does not have to match the case of the corresponding enum constant, which is `INDENT_OUTPUT`.
This environment-based configuration is applied to the auto-configured javadoc:org.springframework.http.converter.json.Jackson2ObjectMapperBuilder[] bean and applies to any mappers created by using the builder, including the auto-configured javadoc:com.fasterxml.jackson.databind.ObjectMapper[] bean. This environment-based configuration is applied to the auto-configured javadoc:tools.jackson.databind.json.JsonMapper.Builder[] bean and applies to any mappers created by using the builder, including the auto-configured javadoc:tools.jackson.databind.json.JsonMapper[] bean.
The context's javadoc:org.springframework.http.converter.json.Jackson2ObjectMapperBuilder[] can be customized by one or more javadoc:org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer[] beans. The context's javadoc:tools.jackson.databind.json.JsonMapper.Builder[] can be customized by one or more javadoc:org.springframework.boot.autoconfigure.jackson.JsonMapperBuilderCustomizer[] beans.
Such customizer beans can be ordered (Boot's own customizer has an order of 0), letting additional customization be applied both before and after Boot's customization. Such customizer beans can be ordered (Boot's own customizer has an order of 0), letting additional customization be applied both before and after Boot's customization.
Any beans of type javadoc:com.fasterxml.jackson.databind.Module[] are automatically registered with the auto-configured javadoc:org.springframework.http.converter.json.Jackson2ObjectMapperBuilder[] and are applied to any javadoc:com.fasterxml.jackson.databind.ObjectMapper[] instances that it creates. Any beans of type javadoc:tools.jackson.databind.JacksonModule[] are automatically registered with the auto-configured javadoc:tools.jackson.databind.json.JsonMapper.Builder[] and are applied to any javadoc:tools.jackson.databind.json.JsonMapper[] instances that it creates.
This provides a global mechanism for contributing custom modules when you add new features to your application. This provides a global mechanism for contributing custom modules when you add new features to your application.
NOTE: If you wish to register additional modules programmatically using a javadoc:org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer[], make sure to use the `modulesToInstall` method that takes a consumer as the other variants are not additive. If you want to replace the default javadoc:tools.jackson.databind.json.JsonMapper[] completely, either define a javadoc:org.springframework.context.annotation.Bean[format=annotation] of that type or, if you prefer the builder-based approach, define a javadoc:tools.jackson.databind.json.JsonMapper.Builder[] javadoc:org.springframework.context.annotation.Bean[format=annotation].
When defining an javadoc:tools.jackson.databind.json.JsonMapper[] bean, marking it as javadoc:org.springframework.context.annotation.Primary[format=annotation] is recommended as the auto-configuration's javadoc:tools.jackson.databind.json.JsonMapper[] that it will replace is javadoc:org.springframework.context.annotation.Primary[format=annotation].
Note that, in either case, doing so disables all auto-configuration of the javadoc:tools.jackson.databind.json.JsonMapper[].
If you want to replace the default javadoc:com.fasterxml.jackson.databind.ObjectMapper[] completely, either define a javadoc:org.springframework.context.annotation.Bean[format=annotation] of that type or, if you prefer the builder-based approach, define a javadoc:org.springframework.http.converter.json.Jackson2ObjectMapperBuilder[] javadoc:org.springframework.context.annotation.Bean[format=annotation]. If you provide any javadoc:java.beans.Beans[format=annotation] of type javadoc:org.springframework.http.converter.json.JacksonJsonHttpMessageConverter[], they replace the default value in the MVC configuration.
When defining an javadoc:com.fasterxml.jackson.databind.ObjectMapper[] bean, marking it as javadoc:org.springframework.context.annotation.Primary[format=annotation] is recommended as the auto-configuration's javadoc:com.fasterxml.jackson.databind.ObjectMapper[] that it will replace is javadoc:org.springframework.context.annotation.Primary[format=annotation].
Note that, in either case, doing so disables all auto-configuration of the javadoc:com.fasterxml.jackson.databind.ObjectMapper[].
If you provide any javadoc:java.beans.Beans[format=annotation] of type javadoc:org.springframework.http.converter.json.MappingJackson2HttpMessageConverter[], they replace the default value in the MVC configuration.
Also, a convenience bean of type javadoc:org.springframework.boot.http.converter.autoconfigure.HttpMessageConverters[] is provided (and is always available if you use the default MVC configuration). Also, a convenience bean of type javadoc:org.springframework.boot.http.converter.autoconfigure.HttpMessageConverters[] is provided (and is always available if you use the default MVC configuration).
It has some useful methods to access the default and user-enhanced message converters. It has some useful methods to access the default and user-enhanced message converters.
@ -137,7 +123,7 @@ See the xref:spring-mvc.adoc#howto.spring-mvc.customize-responsebody-rendering[]
Spring uses javadoc:org.springframework.boot.http.converter.autoconfigure.HttpMessageConverters[] to render javadoc:org.springframework.web.bind.annotation.ResponseBody[format=annotation] (or responses from javadoc:org.springframework.web.bind.annotation.RestController[format=annotation]). Spring uses javadoc:org.springframework.boot.http.converter.autoconfigure.HttpMessageConverters[] to render javadoc:org.springframework.web.bind.annotation.ResponseBody[format=annotation] (or responses from javadoc:org.springframework.web.bind.annotation.RestController[format=annotation]).
You can contribute additional converters by adding beans of the appropriate type in a Spring Boot context. You can contribute additional converters by adding beans of the appropriate type in a Spring Boot context.
If a bean you add is of a type that would have been included by default anyway (such as javadoc:org.springframework.http.converter.json.MappingJackson2HttpMessageConverter[] for JSON conversions), it replaces the default value. If a bean you add is of a type that would have been included by default anyway (such as javadoc:org.springframework.http.converter.json.JacksonJsonHttpMessageConverter[] for JSON conversions), it replaces the default value.
A convenience bean of type javadoc:org.springframework.boot.http.converter.autoconfigure.HttpMessageConverters[] is provided and is always available if you use the default MVC configuration. A convenience bean of type javadoc:org.springframework.boot.http.converter.autoconfigure.HttpMessageConverters[] is provided and is always available if you use the default MVC configuration.
It has some useful methods to access the default and user-enhanced message converters (For example, it can be useful if you want to manually inject them into a custom javadoc:org.springframework.web.client.RestTemplate[]). It has some useful methods to access the default and user-enhanced message converters (For example, it can be useful if you want to manually inject them into a custom javadoc:org.springframework.web.client.RestTemplate[]).

View File

@ -4,7 +4,7 @@
Spring Boot provides integration with three JSON mapping libraries: Spring Boot provides integration with three JSON mapping libraries:
- Gson - Gson
- Jackson - Jackson 3
- JSON-B - JSON-B
Jackson is the preferred and default library. Jackson is the preferred and default library.
@ -15,18 +15,18 @@ Jackson is the preferred and default library.
== Jackson == Jackson
Auto-configuration for Jackson is provided and Jackson is part of `spring-boot-starter-json`. Auto-configuration for Jackson is provided and Jackson is part of `spring-boot-starter-json`.
When Jackson is on the classpath an javadoc:com.fasterxml.jackson.databind.ObjectMapper[] bean is automatically configured. When Jackson is on the classpath a javadoc:tools.jackson.databind.json.JsonMapper[] bean is automatically configured.
Several configuration properties are provided for xref:how-to:spring-mvc.adoc#howto.spring-mvc.customize-jackson-objectmapper[customizing the configuration of the javadoc:com.fasterxml.jackson.databind.ObjectMapper[]]. Several configuration properties are provided for xref:how-to:spring-mvc.adoc#howto.spring-mvc.customize-jackson-jsonmapper[customizing the configuration of the javadoc:tools.jackson.databind.json.JsonMapper[]].
[[features.json.jackson.custom-serializers-and-deserializers]] [[features.json.jackson.custom-serializers-and-deserializers]]
=== Custom Serializers and Deserializers === Custom Serializers and Deserializers
If you use Jackson to serialize and deserialize JSON data, you might want to write your own javadoc:com.fasterxml.jackson.databind.JsonSerializer[] and javadoc:com.fasterxml.jackson.databind.JsonDeserializer[] classes. 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.JsonComponent[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:com.fasterxml.jackson.databind.JsonSerializer[], javadoc:com.fasterxml.jackson.databind.JsonDeserializer[] or javadoc:com.fasterxml.jackson.databind.KeyDeserializer[] implementations. 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 also use it on classes that contain serializers/deserializers as inner classes, as shown in the following example: 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::MyJsonComponent[]
@ -34,10 +34,10 @@ include-code::MyJsonComponent[]
All javadoc:org.springframework.boot.jackson.JsonComponent[format=annotation] beans in the javadoc:org.springframework.context.ApplicationContext[] are automatically registered with Jackson. 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. 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.
Spring Boot also provides javadoc:org.springframework.boot.jackson.JsonObjectSerializer[] and javadoc:org.springframework.boot.jackson.JsonObjectDeserializer[] base classes that provide useful alternatives to the standard Jackson versions when serializing objects. 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.JsonObjectSerializer[] and javadoc:org.springframework.boot.jackson.JsonObjectDeserializer[] in the API documentation for details. 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.JsonObjectSerializer[] and javadoc:org.springframework.boot.jackson.JsonObjectDeserializer[] as follows: 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/MyJsonComponent[]
@ -47,7 +47,7 @@ include-code::object/MyJsonComponent[]
=== Mixins === Mixins
Jackson has support for mixins that can be used to mix additional annotations into those already declared on a target class. 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:com.fasterxml.jackson.databind.ObjectMapper[]. 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[]. The registration is performed by Spring Boot's javadoc:org.springframework.boot.jackson.JsonMixinModule[].

View File

@ -281,7 +281,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.test.autoconfigure.json.JsonTest[format=annotation] annotation. To test that object JSON serialization and deserialization is working as expected, you can use the javadoc:org.springframework.boot.test.autoconfigure.json.JsonTest[format=annotation] annotation.
javadoc:org.springframework.boot.test.autoconfigure.json.JsonTest[format=annotation] auto-configures the available supported JSON mapper, which can be one of the following libraries: javadoc:org.springframework.boot.test.autoconfigure.json.JsonTest[format=annotation] auto-configures the available supported JSON mapper, which can be one of the following libraries:
* Jackson javadoc:com.fasterxml.jackson.databind.ObjectMapper[], any javadoc:org.springframework.boot.jackson.JsonComponent[format=annotation] beans and any Jackson javadoc:com.fasterxml.jackson.databind.Module[] * Jackson javadoc:tools.jackson.databind.JsonMapper[], any javadoc:org.springframework.boot.jackson.JsonComponent[format=annotation] beans and any Jackson javadoc:tools.jackson.databind.JacksonModule[]
* `Gson` * `Gson`
* `Jsonb` * `Jsonb`
@ -317,7 +317,7 @@ javadoc:org.springframework.boot.context.properties.EnableConfigurationPropertie
TIP: A list of the auto-configuration settings that are enabled by javadoc:org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest[format=annotation] can be xref:appendix:test-auto-configuration/index.adoc[found in the appendix]. TIP: A list of the auto-configuration settings that are enabled by javadoc:org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest[format=annotation] can be xref:appendix:test-auto-configuration/index.adoc[found in the appendix].
TIP: If you need to register extra components, such as the Jackson javadoc:com.fasterxml.jackson.databind.Module[], you can import additional configuration classes by using javadoc:org.springframework.context.annotation.Import[format=annotation] on your test. TIP: If you need to register extra components, such as a javadoc:tools.jackson.databind.JacksonModule[], you can import additional configuration classes by using javadoc:org.springframework.context.annotation.Import[format=annotation] on your test.
Often, javadoc:org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest[format=annotation] is limited to a single controller and is used in combination with javadoc:org.springframework.test.context.bean.override.mockito.MockitoBean[format=annotation] to provide mock implementations for required collaborators. Often, javadoc:org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest[format=annotation] is limited to a single controller and is used in combination with javadoc:org.springframework.test.context.bean.override.mockito.MockitoBean[format=annotation] to provide mock implementations for required collaborators.
@ -361,7 +361,7 @@ javadoc:org.springframework.boot.context.properties.EnableConfigurationPropertie
TIP: A list of the auto-configurations that are enabled by javadoc:org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest[format=annotation] can be xref:appendix:test-auto-configuration/index.adoc[found in the appendix]. TIP: A list of the auto-configurations that are enabled by javadoc:org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest[format=annotation] can be xref:appendix:test-auto-configuration/index.adoc[found in the appendix].
TIP: If you need to register extra components, such as Jackson javadoc:com.fasterxml.jackson.databind.Module[], you can import additional configuration classes using javadoc:org.springframework.context.annotation.Import[format=annotation] on your test. TIP: If you need to register extra components, such as a javadoc:tools.jackson.databind.JacksonModule[], you can import additional configuration classes using javadoc:org.springframework.context.annotation.Import[format=annotation] on your test.
Often, javadoc:org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest[format=annotation] is limited to a single controller and used in combination with the javadoc:org.springframework.test.context.bean.override.mockito.MockitoBean[format=annotation] annotation to provide mock implementations for required collaborators. Often, javadoc:org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest[format=annotation] is limited to a single controller and used in combination with the javadoc:org.springframework.test.context.bean.override.mockito.MockitoBean[format=annotation] annotation to provide mock implementations for required collaborators.

View File

@ -2,11 +2,11 @@
= Spring HATEOAS = Spring HATEOAS
If you develop a RESTful API that makes use of hypermedia, Spring Boot provides auto-configuration for Spring HATEOAS that works well with most applications. If you develop a RESTful API that makes use of hypermedia, Spring Boot provides auto-configuration for Spring HATEOAS that works well with most applications.
The auto-configuration replaces the need to use javadoc:org.springframework.hateoas.config.EnableHypermediaSupport[format=annotation] and registers a number of beans to ease building hypermedia-based applications, including a javadoc:org.springframework.hateoas.client.LinkDiscoverers[] (for client side support) and an javadoc:com.fasterxml.jackson.databind.ObjectMapper[] configured to correctly marshal responses into the desired representation. The auto-configuration replaces the need to use javadoc:org.springframework.hateoas.config.EnableHypermediaSupport[format=annotation] and registers a number of beans to ease building hypermedia-based applications, including a javadoc:org.springframework.hateoas.client.LinkDiscoverers[] (for client side support) and an javadoc:tools.jackson.databind.json.JsonMapper[] configured to correctly marshal responses into the desired representation.
The javadoc:com.fasterxml.jackson.databind.ObjectMapper[] is customized by setting the various `spring.jackson.*` properties or, if one exists, by a javadoc:org.springframework.http.converter.json.Jackson2ObjectMapperBuilder[] bean. The javadoc:tools.jackson.databind.json.JsontMapper[] is customized by setting the various `spring.jackson.*` properties or, if any exist, the javadoc:org.springframework.boot.jackson.autoconfigure.JsonMapperBuilderCustomizer[] beans.
You can take control of Spring HATEOAS's configuration by using javadoc:org.springframework.hateoas.config.EnableHypermediaSupport[format=annotation]. You can take control of Spring HATEOAS's configuration by using javadoc:org.springframework.hateoas.config.EnableHypermediaSupport[format=annotation].
Note that doing so disables the javadoc:com.fasterxml.jackson.databind.ObjectMapper[] customization described earlier. Note that doing so disables the javadoc:tools.jackson.databind.json.JsonMapper[] customization described earlier.
WARNING: `spring-boot-starter-hateoas` is specific to Spring MVC and should not be combined with Spring WebFlux. WARNING: `spring-boot-starter-hateoas` is specific to Spring MVC and should not be combined with Spring WebFlux.
In order to use Spring HATEOAS with Spring WebFlux, you can add a direct dependency on `org.springframework.hateoas:spring-hateoas` along with `spring-boot-starter-webflux`. In order to use Spring HATEOAS with Spring WebFlux, you can add a direct dependency on `org.springframework.hateoas:spring-hateoas` along with `spring-boot-starter-webflux`.

View File

@ -16,41 +16,37 @@
package org.springframework.boot.docs.features.json.jackson.customserializersanddeserializers; package org.springframework.boot.docs.features.json.jackson.customserializersanddeserializers;
import java.io.IOException; import tools.jackson.core.JsonGenerator;
import tools.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonGenerator; import tools.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.core.JsonParser; import tools.jackson.databind.JsonNode;
import com.fasterxml.jackson.core.ObjectCodec; import tools.jackson.databind.SerializationContext;
import com.fasterxml.jackson.databind.DeserializationContext; import tools.jackson.databind.ValueDeserializer;
import com.fasterxml.jackson.databind.JsonDeserializer; import tools.jackson.databind.ValueSerializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import org.springframework.boot.jackson.JsonComponent; import org.springframework.boot.jackson.JsonComponent;
@JsonComponent @JsonComponent
public class MyJsonComponent { public class MyJsonComponent {
public static class Serializer extends JsonSerializer<MyObject> { public static class Serializer extends ValueSerializer<MyObject> {
@Override @Override
public void serialize(MyObject value, JsonGenerator jgen, SerializerProvider serializers) throws IOException { public void serialize(MyObject value, JsonGenerator jgen, SerializationContext context) {
jgen.writeStartObject(); jgen.writeStartObject();
jgen.writeStringField("name", value.getName()); jgen.writeStringProperty("name", value.getName());
jgen.writeNumberField("age", value.getAge()); jgen.writeNumberProperty("age", value.getAge());
jgen.writeEndObject(); jgen.writeEndObject();
} }
} }
public static class Deserializer extends JsonDeserializer<MyObject> { public static class Deserializer extends ValueDeserializer<MyObject> {
@Override @Override
public MyObject deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException { public MyObject deserialize(JsonParser jsonParser, DeserializationContext ctxt) {
ObjectCodec codec = jsonParser.getCodec(); JsonNode tree = jsonParser.readValueAsTree();
JsonNode tree = codec.readTree(jsonParser); String name = tree.get("name").stringValue();
String name = tree.get("name").textValue();
int age = tree.get("age").intValue(); int age = tree.get("age").intValue();
return new MyObject(name, age); return new MyObject(name, age);
} }

View File

@ -16,38 +16,33 @@
package org.springframework.boot.docs.features.json.jackson.customserializersanddeserializers.object; package org.springframework.boot.docs.features.json.jackson.customserializersanddeserializers.object;
import java.io.IOException; import tools.jackson.core.JsonGenerator;
import tools.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonGenerator; import tools.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.core.JsonParser; import tools.jackson.databind.JsonNode;
import com.fasterxml.jackson.core.ObjectCodec; import tools.jackson.databind.SerializationContext;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.SerializerProvider;
import org.springframework.boot.jackson.JsonComponent; import org.springframework.boot.jackson.JsonComponent;
import org.springframework.boot.jackson.JsonObjectDeserializer; import org.springframework.boot.jackson.ObjectValueDeserializer;
import org.springframework.boot.jackson.JsonObjectSerializer; import org.springframework.boot.jackson.ObjectValueSerializer;
@JsonComponent @JsonComponent
public class MyJsonComponent { public class MyJsonComponent {
public static class Serializer extends JsonObjectSerializer<MyObject> { public static class Serializer extends ObjectValueSerializer<MyObject> {
@Override @Override
protected void serializeObject(MyObject value, JsonGenerator jgen, SerializerProvider provider) protected void serializeObject(MyObject value, JsonGenerator jgen, SerializationContext context) {
throws IOException { jgen.writeStringProperty("name", value.getName());
jgen.writeStringField("name", value.getName()); jgen.writeNumberProperty("age", value.getAge());
jgen.writeNumberField("age", value.getAge());
} }
} }
public static class Deserializer extends JsonObjectDeserializer<MyObject> { public static class Deserializer extends ObjectValueDeserializer<MyObject> {
@Override @Override
protected MyObject deserializeObject(JsonParser jsonParser, DeserializationContext context, ObjectCodec codec, protected MyObject deserializeObject(JsonParser jsonParser, DeserializationContext context, JsonNode tree) {
JsonNode tree) throws IOException {
String name = nullSafeValue(tree.get("name"), String.class); String name = nullSafeValue(tree.get("name"), String.class);
int age = nullSafeValue(tree.get("age"), Integer.class); int age = nullSafeValue(tree.get("age"), Integer.class);
return new MyObject(name, age); return new MyObject(name, age);

View File

@ -16,36 +16,32 @@
package org.springframework.boot.docs.features.json.jackson.customserializersanddeserializers package org.springframework.boot.docs.features.json.jackson.customserializersanddeserializers
import com.fasterxml.jackson.core.JsonGenerator import tools.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser import tools.jackson.core.JsonParser
import com.fasterxml.jackson.core.JsonProcessingException import tools.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.DeserializationContext import tools.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.JsonDeserializer import tools.jackson.databind.SerializationContext
import com.fasterxml.jackson.databind.JsonNode import tools.jackson.databind.ValueDeserializer
import com.fasterxml.jackson.databind.JsonSerializer import tools.jackson.databind.ValueSerializer
import com.fasterxml.jackson.databind.SerializerProvider
import org.springframework.boot.jackson.JsonComponent import org.springframework.boot.jackson.JsonComponent
import java.io.IOException
@JsonComponent @JsonComponent
class MyJsonComponent { class MyJsonComponent {
class Serializer : JsonSerializer<MyObject>() { class Serializer : ValueSerializer<MyObject>() {
@Throws(IOException::class) override fun serialize(value: MyObject, jgen: JsonGenerator, serializers: SerializationContext) {
override fun serialize(value: MyObject, jgen: JsonGenerator, serializers: SerializerProvider) {
jgen.writeStartObject() jgen.writeStartObject()
jgen.writeStringField("name", value.name) jgen.writeStringProperty("name", value.name)
jgen.writeNumberField("age", value.age) jgen.writeNumberProperty("age", value.age)
jgen.writeEndObject() jgen.writeEndObject()
} }
} }
class Deserializer : JsonDeserializer<MyObject>() { class Deserializer : ValueDeserializer<MyObject>() {
@Throws(IOException::class, JsonProcessingException::class)
override fun deserialize(jsonParser: JsonParser, ctxt: DeserializationContext): MyObject { override fun deserialize(jsonParser: JsonParser, ctxt: DeserializationContext): MyObject {
val codec = jsonParser.codec val tree = jsonParser.readValueAsTree<JsonNode>()
val tree = codec.readTree<JsonNode>(jsonParser) val name = tree["name"].stringValue()
val name = tree["name"].textValue()
val age = tree["age"].intValue() val age = tree["age"].intValue()
return MyObject(name, age) return MyObject(name, age)
} }

View File

@ -16,32 +16,29 @@
package org.springframework.boot.docs.features.json.jackson.customserializersanddeserializers.`object` package org.springframework.boot.docs.features.json.jackson.customserializersanddeserializers.`object`
import com.fasterxml.jackson.core.JsonGenerator import tools.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser import tools.jackson.core.JsonParser
import com.fasterxml.jackson.core.ObjectCodec import tools.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.DeserializationContext import tools.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.JsonNode import tools.jackson.databind.SerializationContext
import com.fasterxml.jackson.databind.SerializerProvider
import org.springframework.boot.jackson.JsonComponent import org.springframework.boot.jackson.JsonComponent;
import org.springframework.boot.jackson.JsonObjectDeserializer import org.springframework.boot.jackson.ObjectValueDeserializer
import org.springframework.boot.jackson.JsonObjectSerializer import org.springframework.boot.jackson.ObjectValueSerializer
import java.io.IOException
@JsonComponent @JsonComponent
class MyJsonComponent { class MyJsonComponent {
class Serializer : JsonObjectSerializer<MyObject>() { class Serializer : ObjectValueSerializer<MyObject>() {
@Throws(IOException::class) override fun serializeObject(value: MyObject, jgen: JsonGenerator, context: SerializationContext) {
override fun serializeObject(value: MyObject, jgen: JsonGenerator, provider: SerializerProvider) { jgen.writeStringProperty("name", value.name)
jgen.writeStringField("name", value.name) jgen.writeNumberProperty("age", value.age)
jgen.writeNumberField("age", value.age)
} }
} }
class Deserializer : JsonObjectDeserializer<MyObject>() { class Deserializer : ObjectValueDeserializer<MyObject>() {
@Throws(IOException::class)
override fun deserializeObject(jsonParser: JsonParser, context: DeserializationContext, override fun deserializeObject(jsonParser: JsonParser, context: DeserializationContext,
codec: ObjectCodec, tree: JsonNode): MyObject { tree: JsonNode): MyObject {
val name = nullSafeValue(tree["name"], String::class.java) ?: throw IllegalStateException("name is null") val name = nullSafeValue(tree["name"], String::class.java) ?: throw IllegalStateException("name is null")
val age = nullSafeValue(tree["age"], Int::class.java) ?: throw IllegalStateException("age is null") val age = nullSafeValue(tree["age"], Int::class.java) ?: throw IllegalStateException("age is null")
return MyObject(name, age) return MyObject(name, age)

View File

@ -11,7 +11,8 @@ checkstyleToolVersion=10.12.4
commonsCodecVersion=1.19.0 commonsCodecVersion=1.19.0
graalVersion=22.3 graalVersion=22.3
hamcrestVersion=3.0 hamcrestVersion=3.0
jacksonVersion=2.20.0-rc1 jackson2Version=2.20.0
jacksonVersion=3.0.0-rc8
javaFormatVersion=0.0.47 javaFormatVersion=0.0.47
junitJupiterVersion=5.13.4 junitJupiterVersion=5.13.4
kotlinVersion=2.2.0 kotlinVersion=2.2.0

View File

@ -43,4 +43,5 @@ dependencies {
testImplementation("net.minidev:json-smart") testImplementation("net.minidev:json-smart")
testImplementation("org.springframework.security:spring-security-web") testImplementation("org.springframework.security:spring-security-web")
testRuntimeOnly("ch.qos.logback:logback-classic") testRuntimeOnly("ch.qos.logback:logback-classic")
testRuntimeOnly("com.fasterxml.jackson.datatype:jackson-datatype-jsr310") // Required for Jersey
} }

View File

@ -16,16 +16,15 @@
package org.springframework.boot.actuate.metrics; package org.springframework.boot.actuate.metrics;
import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.MockClock; import io.micrometer.core.instrument.MockClock;
import io.micrometer.core.instrument.binder.jvm.JvmMemoryMetrics; import io.micrometer.core.instrument.binder.jvm.JvmMemoryMetrics;
import io.micrometer.core.instrument.simple.SimpleConfig; import io.micrometer.core.instrument.simple.SimpleConfig;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import tools.jackson.databind.ObjectMapper;
import org.springframework.boot.actuate.endpoint.web.test.WebEndpointTest; import org.springframework.boot.actuate.endpoint.web.test.WebEndpointTest;
import org.springframework.boot.micrometer.metrics.actuate.endpoint.MetricsEndpoint; import org.springframework.boot.micrometer.metrics.actuate.endpoint.MetricsEndpoint;
@ -49,7 +48,7 @@ class MetricsEndpointWebIntegrationTests {
@WebEndpointTest @WebEndpointTest
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
void listNames(WebTestClient client) throws IOException { void listNames(WebTestClient client) {
String responseBody = client.get() String responseBody = client.get()
.uri("/actuator/metrics") .uri("/actuator/metrics")
.exchange() .exchange()

View File

@ -16,6 +16,12 @@
package org.springframework.boot.activemq.autoconfigure; package org.springframework.boot.activemq.autoconfigure;
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.jms.ConnectionFactory; import jakarta.jms.ConnectionFactory;
import org.apache.activemq.ActiveMQConnectionFactory; import org.apache.activemq.ActiveMQConnectionFactory;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -47,6 +53,14 @@ class ActiveMQAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(ActiveMQAutoConfiguration.class, JmsAutoConfiguration.class)); .withConfiguration(AutoConfigurations.of(ActiveMQAutoConfiguration.class, JmsAutoConfiguration.class));
@Test
void dunno() throws JsonProcessingException {
Map<String, String> map = new HashMap<>();
map.put("key", null);
String string = new ObjectMapper().setDefaultPropertyInclusion(Include.NON_NULL).writeValueAsString(map);
System.out.println(string);
}
@Test @Test
void brokerIsEmbeddedByDefault() { void brokerIsEmbeddedByDefault() {
this.contextRunner.withUserConfiguration(EmptyConfiguration.class).run((context) -> { this.contextRunner.withUserConfiguration(EmptyConfiguration.class).run((context) -> {

View File

@ -29,13 +29,11 @@ dependencies {
api(project(":core:spring-boot-autoconfigure")) api(project(":core:spring-boot-autoconfigure"))
api(project(":module:spring-boot-actuator")) api(project(":module:spring-boot-actuator"))
implementation("com.fasterxml.jackson.core:jackson-databind") implementation("tools.jackson.core:jackson-databind")
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
optional(project(":module:spring-boot-health")) optional(project(":module:spring-boot-health"))
optional(project(":module:spring-boot-web-server")) optional(project(":module:spring-boot-web-server"))
optional("com.fasterxml.jackson.core:jackson-databind")
optional("io.micrometer:micrometer-core") optional("io.micrometer:micrometer-core")
optional("io.projectreactor:reactor-core") optional("io.projectreactor:reactor-core")
optional("jakarta.servlet:jakarta.servlet-api") optional("jakarta.servlet:jakarta.servlet-api")

View File

@ -20,17 +20,15 @@ import java.util.HashSet;
import java.util.Set; import java.util.Set;
import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.databind.MapperFeature; import tools.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectMapper; import tools.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.springframework.boot.actuate.endpoint.jackson.EndpointObjectMapper; import org.springframework.boot.actuate.endpoint.jackson.EndpointJsonMapper;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
/** /**
@ -40,30 +38,27 @@ import org.springframework.util.ClassUtils;
* @since 3.0.0 * @since 3.0.0
*/ */
@AutoConfiguration @AutoConfiguration
@SuppressWarnings("removal")
public final class JacksonEndpointAutoConfiguration { public final class JacksonEndpointAutoConfiguration {
private static final String CONTRIBUTED_HEALTH = "org.springframework.boot.health.contributor.ContributedHealth"; private static final String CONTRIBUTED_HEALTH = "org.springframework.boot.health.contributor.ContributedHealth";
@Bean @Bean
@ConditionalOnBooleanProperty(name = "management.endpoints.jackson.isolated-object-mapper", matchIfMissing = true) @ConditionalOnBooleanProperty(name = "management.endpoints.jackson.isolated-object-mapper", matchIfMissing = true)
@ConditionalOnClass({ ObjectMapper.class, Jackson2ObjectMapperBuilder.class }) @ConditionalOnClass(ObjectMapper.class)
EndpointObjectMapper endpointObjectMapper() { EndpointJsonMapper endpointJsonMapper() {
ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json() JsonMapper jsonMapper = JsonMapper.builder()
.featuresToEnable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY) .changeDefaultPropertyInclusion(
.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, (value) -> value.withValueInclusion(Include.NON_NULL).withContentInclusion(Include.NON_NULL))
SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS)
.serializationInclusion(Include.NON_NULL)
.build(); .build();
Set<Class<?>> supportedTypes = new HashSet<>(EndpointObjectMapper.DEFAULT_SUPPORTED_TYPES); Set<Class<?>> supportedTypes = new HashSet<>(EndpointJsonMapper.DEFAULT_SUPPORTED_TYPES);
if (ClassUtils.isPresent(CONTRIBUTED_HEALTH, null)) { if (ClassUtils.isPresent(CONTRIBUTED_HEALTH, null)) {
supportedTypes.add(ClassUtils.resolveClassName(CONTRIBUTED_HEALTH, null)); supportedTypes.add(ClassUtils.resolveClassName(CONTRIBUTED_HEALTH, null));
} }
return new EndpointObjectMapper() { return new EndpointJsonMapper() {
@Override @Override
public ObjectMapper get() { public JsonMapper get() {
return objectMapper; return jsonMapper;
} }
@Override @Override

View File

@ -18,7 +18,7 @@ package org.springframework.boot.actuate.autoconfigure.endpoint.jmx;
import javax.management.MBeanServer; import javax.management.MBeanServer;
import com.fasterxml.jackson.databind.ObjectMapper; import tools.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.LazyInitializationExcludeFilter; import org.springframework.boot.LazyInitializationExcludeFilter;

View File

@ -22,10 +22,10 @@ import java.time.format.DateTimeFormatter;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.boot.actuate.endpoint.jackson.EndpointObjectMapper; import org.springframework.boot.actuate.endpoint.jackson.EndpointJsonMapper;
import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@ -44,49 +44,49 @@ class JacksonEndpointAutoConfigurationTests {
.withConfiguration(AutoConfigurations.of(JacksonEndpointAutoConfiguration.class)); .withConfiguration(AutoConfigurations.of(JacksonEndpointAutoConfiguration.class));
@Test @Test
void endpointObjectMapperWhenNoProperty() { void endpointJsonMapperWhenNoProperty() {
this.runner.run((context) -> assertThat(context).hasSingleBean(EndpointObjectMapper.class)); this.runner.run((context) -> assertThat(context).hasSingleBean(EndpointJsonMapper.class));
} }
@Test @Test
void endpointObjectMapperWhenPropertyTrue() { void endpointJsonMapperWhenPropertyTrue() {
this.runner.withPropertyValues("management.endpoints.jackson.isolated-object-mapper=true") this.runner.withPropertyValues("management.endpoints.jackson.isolated-object-mapper=true")
.run((context) -> assertThat(context).hasSingleBean(EndpointObjectMapper.class)); .run((context) -> assertThat(context).hasSingleBean(EndpointJsonMapper.class));
} }
@Test @Test
void endpointObjectMapperWhenPropertyFalse() { void endpointJsonMapperWhenPropertyFalse() {
this.runner.withPropertyValues("management.endpoints.jackson.isolated-object-mapper=false") this.runner.withPropertyValues("management.endpoints.jackson.isolated-object-mapper=false")
.run((context) -> assertThat(context).doesNotHaveBean(EndpointObjectMapper.class)); .run((context) -> assertThat(context).doesNotHaveBean(EndpointJsonMapper.class));
} }
@Test @Test
void endpointObjectMapperDoesNotSerializeDatesAsTimestamps() { void endpointJsonMapperDoesNotSerializeDatesAsTimestamps() {
this.runner.run((context) -> { this.runner.run((context) -> {
ObjectMapper objectMapper = context.getBean(EndpointObjectMapper.class).get(); JsonMapper jsonMapper = context.getBean(EndpointJsonMapper.class).get();
Instant now = Instant.now(); Instant now = Instant.now();
String json = objectMapper.writeValueAsString(Map.of("timestamp", now)); String json = jsonMapper.writeValueAsString(Map.of("timestamp", now));
assertThat(json).contains(DateTimeFormatter.ISO_INSTANT.format(now)); assertThat(json).contains(DateTimeFormatter.ISO_INSTANT.format(now));
}); });
} }
@Test @Test
void endpointObjectMapperDoesNotSerializeDurationsAsTimestamps() { void endpointJsonMapperDoesNotSerializeDurationsAsTimestamps() {
this.runner.run((context) -> { this.runner.run((context) -> {
ObjectMapper objectMapper = context.getBean(EndpointObjectMapper.class).get(); JsonMapper jsonMapper = context.getBean(EndpointJsonMapper.class).get();
Duration duration = Duration.ofSeconds(42); Duration duration = Duration.ofSeconds(42);
String json = objectMapper.writeValueAsString(Map.of("duration", duration)); String json = jsonMapper.writeValueAsString(Map.of("duration", duration));
assertThat(json).contains(duration.toString()); assertThat(json).contains(duration.toString());
}); });
} }
@Test @Test
void endpointObjectMapperDoesNotSerializeNullValues() { void endpointJsonMapperDoesNotSerializeNullValues() {
this.runner.run((context) -> { this.runner.run((context) -> {
ObjectMapper objectMapper = context.getBean(EndpointObjectMapper.class).get(); JsonMapper jsonMapper = context.getBean(EndpointJsonMapper.class).get();
HashMap<String, String> map = new HashMap<>(); HashMap<String, String> map = new HashMap<>();
map.put("key", null); map.put("key", null);
String json = objectMapper.writeValueAsString(map); String json = jsonMapper.writeValueAsString(map);
assertThat(json).isEqualTo("{}"); assertThat(json).isEqualTo("{}");
}); });
} }
@ -95,16 +95,16 @@ class JacksonEndpointAutoConfigurationTests {
static class TestEndpointMapperConfiguration { static class TestEndpointMapperConfiguration {
@Bean @Bean
TestEndpointObjectMapper testEndpointObjectMapper() { TestEndpointJsonMapper testEndpointJsonMapper() {
return new TestEndpointObjectMapper(); return new TestEndpointJsonMapper();
} }
} }
static class TestEndpointObjectMapper implements EndpointObjectMapper { static class TestEndpointJsonMapper implements EndpointJsonMapper {
@Override @Override
public ObjectMapper get() { public JsonMapper get() {
return null; return null;
} }

View File

@ -32,8 +32,6 @@ dependencies {
optional(project(":module:spring-boot-jsonb")) optional(project(":module:spring-boot-jsonb"))
optional(project(":module:spring-boot-validation")) optional(project(":module:spring-boot-validation"))
optional(project(":module:spring-boot-web-server")) optional(project(":module:spring-boot-web-server"))
optional("com.fasterxml.jackson.core:jackson-databind")
optional("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
optional("com.github.ben-manes.caffeine:caffeine") optional("com.github.ben-manes.caffeine:caffeine")
optional("com.google.code.findbugs:jsr305") optional("com.google.code.findbugs:jsr305")
optional("com.zaxxer:HikariCP") optional("com.zaxxer:HikariCP")
@ -57,8 +55,8 @@ dependencies {
optional("org.springframework.graphql:spring-graphql") optional("org.springframework.graphql:spring-graphql")
optional("org.springframework.security:spring-security-core") optional("org.springframework.security:spring-security-core")
optional("org.springframework.security:spring-security-web") optional("org.springframework.security:spring-security-web")
optional("tools.jackson.core:jackson-databind")
testFixturesApi("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-api") testFixturesImplementation("org.junit.jupiter:junit-jupiter-api")
testFixturesImplementation("org.springframework:spring-test") testFixturesImplementation("org.springframework:spring-test")
testFixturesImplementation("org.springframework:spring-webflux") testFixturesImplementation("org.springframework:spring-webflux")

View File

@ -29,30 +29,30 @@ import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.BeanSerializerFactory;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
import com.fasterxml.jackson.databind.ser.PropertyWriter;
import com.fasterxml.jackson.databind.ser.SerializerFactory;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import tools.jackson.core.JsonGenerator;
import tools.jackson.databind.BeanDescription;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.SerializationConfig;
import tools.jackson.databind.SerializationContext;
import tools.jackson.databind.SerializationFeature;
import tools.jackson.databind.cfg.MapperConfig;
import tools.jackson.databind.introspect.Annotated;
import tools.jackson.databind.introspect.AnnotatedMethod;
import tools.jackson.databind.introspect.DefaultAccessorNamingStrategy;
import tools.jackson.databind.introspect.JacksonAnnotationIntrospector;
import tools.jackson.databind.json.JsonMapper;
import tools.jackson.databind.module.SimpleModule;
import tools.jackson.databind.ser.BeanPropertyWriter;
import tools.jackson.databind.ser.BeanSerializerFactory;
import tools.jackson.databind.ser.PropertyWriter;
import tools.jackson.databind.ser.SerializerFactory;
import tools.jackson.databind.ser.ValueSerializerModifier;
import tools.jackson.databind.ser.std.SimpleBeanPropertyFilter;
import tools.jackson.databind.ser.std.SimpleFilterProvider;
import tools.jackson.databind.ser.std.ToStringSerializer;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.boot.actuate.endpoint.OperationResponseBody; import org.springframework.boot.actuate.endpoint.OperationResponseBody;
@ -180,13 +180,10 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
*/ */
protected void configureJsonMapper(JsonMapper.Builder builder) { protected void configureJsonMapper(JsonMapper.Builder builder) {
builder.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); builder.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
builder.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); builder.changeDefaultPropertyInclusion((value) -> value.withValueInclusion(Include.NON_NULL));
builder.configure(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS, false); builder.accessorNaming(new DefaultAccessorNamingStrategy.Provider().withFirstCharAcceptance(true, false));
builder.configure(MapperFeature.USE_STD_BEAN_NAMING, true);
builder.serializationInclusion(Include.NON_NULL);
applyConfigurationPropertiesFilter(builder); applyConfigurationPropertiesFilter(builder);
applySerializationModifier(builder); applySerializationModifier(builder);
builder.addModule(new JavaTimeModule());
builder.addModule(new ConfigurationPropertiesModule()); builder.addModule(new ConfigurationPropertiesModule());
} }
@ -411,8 +408,8 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
private static final class ConfigurationPropertiesAnnotationIntrospector extends JacksonAnnotationIntrospector { private static final class ConfigurationPropertiesAnnotationIntrospector extends JacksonAnnotationIntrospector {
@Override @Override
public Object findFilterId(Annotated a) { public Object findFilterId(MapperConfig<?> config, Annotated a) {
Object id = super.findFilterId(a); Object id = super.findFilterId(config, a);
if (id == null) { if (id == null) {
id = CONFIGURATION_PROPERTIES_FILTER_ID; id = CONFIGURATION_PROPERTIES_FILTER_ID;
} }
@ -450,7 +447,7 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
} }
@Override @Override
public void serializeAsField(Object pojo, JsonGenerator jgen, SerializerProvider provider, public void serializeAsProperty(Object pojo, JsonGenerator jgen, SerializationContext context,
PropertyWriter writer) throws Exception { PropertyWriter writer) throws Exception {
if (writer instanceof BeanPropertyWriter beanPropertyWriter) { if (writer instanceof BeanPropertyWriter beanPropertyWriter) {
try { try {
@ -470,7 +467,7 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
return; return;
} }
} }
super.serializeAsField(pojo, jgen, provider, writer); super.serializeAsProperty(pojo, jgen, context, writer);
} }
} }
@ -487,14 +484,14 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
} }
/** /**
* {@link BeanSerializerModifier} to return only relevant configuration properties. * {@link ValueSerializerModifier} to return only relevant configuration properties.
*/ */
protected static class GenericSerializerModifier extends BeanSerializerModifier { protected static class GenericSerializerModifier extends ValueSerializerModifier {
private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer(); private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer();
@Override @Override
public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription.Supplier beanDesc,
List<BeanPropertyWriter> beanProperties) { List<BeanPropertyWriter> beanProperties) {
List<BeanPropertyWriter> result = new ArrayList<>(); List<BeanPropertyWriter> result = new ArrayList<>();
Class<?> beanClass = beanDesc.getType().getRawClass(); Class<?> beanClass = beanDesc.getType().getRawClass();
@ -508,7 +505,7 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
return result; return result;
} }
private boolean isCandidate(BeanDescription beanDesc, BeanPropertyWriter writer, private boolean isCandidate(BeanDescription.Supplier beanDesc, BeanPropertyWriter writer,
@Nullable Constructor<?> constructor) { @Nullable Constructor<?> constructor) {
if (constructor != null) { if (constructor != null) {
Parameter[] parameters = constructor.getParameters(); Parameter[] parameters = constructor.getParameters();
@ -529,10 +526,10 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
return isReadable(beanDesc, writer); return isReadable(beanDesc, writer);
} }
private boolean isReadable(BeanDescription beanDesc, BeanPropertyWriter writer) { private boolean isReadable(BeanDescription.Supplier beanDesc, BeanPropertyWriter writer) {
Class<?> parentType = beanDesc.getType().getRawClass(); Class<?> parentType = beanDesc.get().getType().getRawClass();
Class<?> type = writer.getType().getRawClass(); Class<?> type = writer.getType().getRawClass();
AnnotatedMethod setter = findSetter(beanDesc, writer); AnnotatedMethod setter = findSetter(beanDesc.get(), writer);
// If there's a setter, we assume it's OK to report on the value, // If there's a setter, we assume it's OK to report on the value,
// similarly, if there's no setter but the package names match, we assume // similarly, if there's no setter but the package names match, we assume
// that it is a nested class used solely for binding to config props, so it // that it is a nested class used solely for binding to config props, so it

View File

@ -18,8 +18,8 @@ package org.springframework.boot.actuate.endpoint;
import java.util.Map; import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import tools.jackson.databind.ObjectMapper;
import org.springframework.lang.Contract; import org.springframework.lang.Contract;

View File

@ -18,19 +18,19 @@ package org.springframework.boot.actuate.endpoint.jackson;
import java.util.Set; import java.util.Set;
import com.fasterxml.jackson.databind.ObjectMapper; import tools.jackson.databind.json.JsonMapper;
import org.springframework.boot.actuate.endpoint.OperationResponseBody; import org.springframework.boot.actuate.endpoint.OperationResponseBody;
/** /**
* Interface used to supply the {@link ObjectMapper} that should be used when serializing * Interface used to supply the {@link JsonMapper} that should be used when serializing
* endpoint results. * endpoint results.
* *
* @author Phillip Webb * @author Phillip Webb
* @since 3.0.0 * @since 4.0.0
* @see OperationResponseBody * @see OperationResponseBody
*/ */
public interface EndpointObjectMapper { public interface EndpointJsonMapper {
/** /**
* The default supported types. * The default supported types.
@ -38,16 +38,15 @@ public interface EndpointObjectMapper {
Set<Class<?>> DEFAULT_SUPPORTED_TYPES = Set.of(OperationResponseBody.class); Set<Class<?>> DEFAULT_SUPPORTED_TYPES = Set.of(OperationResponseBody.class);
/** /**
* Return the {@link ObjectMapper} that should be used to serialize * Return the {@link JsonMapper} that should be used to serialize
* {@link OperationResponseBody} endpoint results. * {@link OperationResponseBody} endpoint results.
* @return the object mapper * @return the object mapper
*/ */
ObjectMapper get(); JsonMapper get();
/** /**
* Return the types that this endpoint mapper supports. * Return the types that this endpoint mapper supports.
* @return the supported types * @return the supported types
* @since 4.0.0
*/ */
default Set<Class<?>> getSupportedTypes() { default Set<Class<?>> getSupportedTypes() {
return DEFAULT_SUPPORTED_TYPES; return DEFAULT_SUPPORTED_TYPES;

View File

@ -20,9 +20,9 @@ import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import tools.jackson.databind.JavaType;
import tools.jackson.databind.ObjectMapper;
/** /**
* {@link JmxOperationResponseMapper} that delegates to a Jackson {@link ObjectMapper} to * {@link JmxOperationResponseMapper} that delegates to a Jackson {@link ObjectMapper} to

View File

@ -20,6 +20,7 @@ import java.util.Collections;
import org.json.JSONObject; import org.json.JSONObject;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import tools.jackson.databind.ObjectMapper;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
@ -69,13 +70,10 @@ class AuditEventTests {
} }
@Test @Test
@SuppressWarnings({ "removal", "deprecation" })
void jsonFormat() throws Exception { void jsonFormat() throws Exception {
AuditEvent event = new AuditEvent("johannes", "UNKNOWN", AuditEvent event = new AuditEvent("johannes", "UNKNOWN",
Collections.singletonMap("type", (Object) "BadCredentials")); Collections.singletonMap("type", (Object) "BadCredentials"));
String json = org.springframework.http.converter.json.Jackson2ObjectMapperBuilder.json() String json = new ObjectMapper().writeValueAsString(event);
.build()
.writeValueAsString(event);
JSONObject jsonObject = new JSONObject(json); JSONObject jsonObject = new JSONObject(json);
assertThat(jsonObject.getString("type")).isEqualTo("UNKNOWN"); assertThat(jsonObject.getString("type")).isEqualTo("UNKNOWN");
assertThat(jsonObject.getJSONObject("data").getString("type")).isEqualTo("BadCredentials"); assertThat(jsonObject.getJSONObject("data").getString("type")).isEqualTo("BadCredentials");

View File

@ -179,7 +179,7 @@ class ConfigurationPropertiesReportEndpointTests {
void descriptorWithMixedCaseProperty() { void descriptorWithMixedCaseProperty() {
this.contextRunner.withUserConfiguration(MixedCasePropertiesConfiguration.class) this.contextRunner.withUserConfiguration(MixedCasePropertiesConfiguration.class)
.run(assertProperties("mixedcase", .run(assertProperties("mixedcase",
(properties) -> assertThat(properties.get("mIxedCase")).isEqualTo("mixed"))); (properties) -> assertThat(properties).containsEntry("mIxedCase", "mixed")));
} }
@Test @Test

View File

@ -23,9 +23,9 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import tools.jackson.databind.JavaType;
import tools.jackson.databind.ObjectMapper;
import org.springframework.boot.test.json.BasicJsonTester; import org.springframework.boot.test.json.BasicJsonTester;

View File

@ -20,10 +20,10 @@ import java.util.Collections;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import tools.jackson.databind.MapperFeature;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.boot.actuate.endpoint.ApiVersion; import org.springframework.boot.actuate.endpoint.ApiVersion;
import org.springframework.boot.health.contributor.Health; import org.springframework.boot.health.contributor.Health;

View File

@ -16,10 +16,10 @@
package org.springframework.boot.actuate.health; package org.springframework.boot.actuate.health;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import tools.jackson.databind.MapperFeature;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.boot.health.contributor.Health; import org.springframework.boot.health.contributor.Health;

View File

@ -22,10 +22,10 @@ import java.util.LinkedHashSet;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import tools.jackson.databind.MapperFeature;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.boot.actuate.endpoint.ApiVersion; import org.springframework.boot.actuate.endpoint.ApiVersion;
import org.springframework.boot.health.contributor.Health; import org.springframework.boot.health.contributor.Health;

View File

@ -33,6 +33,7 @@ dependencies {
optional(project(":core:spring-boot-testcontainers")) optional(project(":core:spring-boot-testcontainers"))
optional(project(":module:spring-boot-health")) optional(project(":module:spring-boot-health"))
optional(project(":module:spring-boot-jackson")) optional(project(":module:spring-boot-jackson"))
optional("com.fasterxml.jackson.core:jackson-databind")
optional("org.testcontainers:couchbase") optional("org.testcontainers:couchbase")
dockerTestImplementation(project(":core:spring-boot-test")) dockerTestImplementation(project(":core:spring-boot-test"))

View File

@ -17,7 +17,6 @@
package org.springframework.boot.couchbase.autoconfigure; package org.springframework.boot.couchbase.autoconfigure;
import java.time.Duration; import java.time.Duration;
import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -33,6 +32,7 @@ import com.couchbase.client.java.codec.JsonSerializer;
import com.couchbase.client.java.env.ClusterEnvironment; import com.couchbase.client.java.env.ClusterEnvironment;
import com.couchbase.client.java.json.JsonValueModule; import com.couchbase.client.java.json.JsonValueModule;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.assertj.core.api.InstanceOfAssertFactories; import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -129,14 +129,14 @@ class CouchbaseAutoConfigurationTests {
@Test @Test
void whenObjectMapperBeanIsDefinedThenClusterEnvironmentObjectMapperIsDerivedFromIt() { void whenObjectMapperBeanIsDefinedThenClusterEnvironmentObjectMapperIsDerivedFromIt() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new SimpleModule("custom"));
this.contextRunner.withUserConfiguration(CouchbaseTestConfiguration.class) this.contextRunner.withUserConfiguration(CouchbaseTestConfiguration.class)
.withConfiguration(AutoConfigurations.of(JacksonAutoConfiguration.class)) .withBean(ObjectMapper.class, () -> objectMapper)
.withPropertyValues("spring.couchbase.connection-string=localhost") .withPropertyValues("spring.couchbase.connection-string=localhost")
.run((context) -> { .run((context) -> {
ClusterEnvironment env = context.getBean(ClusterEnvironment.class); ClusterEnvironment env = context.getBean(ClusterEnvironment.class);
Set<Object> expectedModuleIds = new HashSet<>( Set<Object> expectedModuleIds = Set.of("custom", new JsonValueModule().getTypeId());
context.getBean(ObjectMapper.class).getRegisteredModuleIds());
expectedModuleIds.add(new JsonValueModule().getTypeId());
JsonSerializer serializer = env.jsonSerializer(); JsonSerializer serializer = env.jsonSerializer();
assertThat(serializer).extracting("wrapped") assertThat(serializer).extracting("wrapped")
.isInstanceOf(JacksonJsonSerializer.class) .isInstanceOf(JacksonJsonSerializer.class)

Some files were not shown because too many files have changed in this diff Show More