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-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("net.java.dev.jna:jna-platform")
testImplementation("org.apache.commons:commons-compress")
@ -70,6 +68,7 @@ dependencies {
testImplementation("org.jetbrains.kotlin:kotlin-compiler-runner:$kotlinVersion")
testImplementation("org.jetbrains.kotlin:kotlin-daemon-client:$kotlinVersion")
testImplementation("org.tomlj:tomlj:1.0.0")
testImplementation("tools.jackson.core:jackson-databind")
}
repositories {

View File

@ -22,9 +22,6 @@ import java.util.ArrayList;
import java.util.List;
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 io.spring.gradle.dependencymanagement.DependencyManagementPlugin;
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.tooling.core.KotlinToolingVersion;
import org.tomlj.Toml;
import tools.jackson.core.JsonParser;
import tools.jackson.databind.JacksonModule;
import org.springframework.asm.ClassVisitor;
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(HttpRequest.class)));
classpath.add(new File(pathOfJarContaining(HttpVersionPolicy.class)));
classpath.add(new File(pathOfJarContaining(Module.class)));
classpath.add(new File(pathOfJarContaining(Versioned.class)));
classpath.add(new File(pathOfJarContaining(ParameterNamesModule.class)));
classpath.add(new File(pathOfJarContaining(JacksonModule.class)));
classpath.add(new File(pathOfJarContaining(JsonParser.class)));
classpath.add(new File(pathOfJarContaining("com.github.openjson.JSONObject")));
classpath.add(new File(pathOfJarContaining(JsonView.class)));
classpath.add(new File(pathOfJarContaining(Platform.class)));

View File

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

View File

@ -6,4 +6,11 @@
<module name="io.spring.javaformat.checkstyle.SpringChecks">
<property name="excludes" value="com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocPackageCheck" />
</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;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@ -26,7 +24,6 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.gradle.node.NodeExtension;
import com.github.gradle.node.npm.task.NpmInstallTask;
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.TaskContainer;
import org.gradle.api.tasks.TaskProvider;
import tools.jackson.databind.ObjectMapper;
import org.springframework.boot.build.antora.AntoraAsciidocAttributes;
import org.springframework.boot.build.antora.GenerateAntoraPlaybook;
@ -198,7 +196,6 @@ public class AntoraConventions {
}
private String getUiBundleUrl(Project project) {
try {
File packageJson = project.getRootProject().file("antora/package.json");
ObjectMapper objectMapper = new ObjectMapper();
Map<?, ?> json = objectMapper.readerFor(Map.class).readValue(packageJson);
@ -207,10 +204,6 @@ public class AntoraConventions {
Assert.state(StringUtils.hasText(url.toString()), "package.json has not ui-bundle-url config");
return url;
}
catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
private void configureNodeExtension(Project project, NodeExtension nodeExtension) {
nodeExtension.getWorkDir().set(project.getLayout().getBuildDirectory().dir(".gradle/nodejs"));

View File

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

View File

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

View File

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

View File

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

View File

@ -26,9 +26,6 @@ import java.util.Iterator;
import java.util.List;
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.file.RegularFileProperty;
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.TaskAction;
import org.gradle.api.tasks.VerificationException;
import tools.jackson.databind.ObjectMapper;
/**
* {@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();
@TaskAction
void check() throws JsonParseException, IOException {
void check() throws IOException {
Report report = createReport();
File reportFile = getReportLocation().get().getAsFile();
Files.write(reportFile.toPath(), report, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
@ -76,7 +74,7 @@ public abstract class CheckSpringConfigurationMetadata extends DefaultTask {
}
@SuppressWarnings("unchecked")
private Report createReport() throws IOException, JsonParseException, JsonMappingException {
private Report createReport() {
ObjectMapper objectMapper = new ObjectMapper();
File file = getMetadataLocation().get().getAsFile();
Report report = new Report(this.projectRoot.relativize(file.toPath()));

View File

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

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-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-core-javadoc=https://javadoc.io/doc/com.fasterxml.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-dataformat-xml-javadoc=https://javadoc.io/doc/com.fasterxml.jackson.dataformat/jackson-dataformat-xml/{version-jackson-dataformat-xml}
url-jackson-core-javadoc=https://javadoc.io/doc/tools.jackson.core/jackson-core/{version-jackson-core}
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/tools.jackson.dataformat/jackson-dataformat-xml/{version-jackson-dataformat-xml}
# === 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-reactive-client-api={url-pulsar-client-reactive-api-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-redis={url-spring-data-redis-javadoc}
javadoc-location-org-springframework-data-rest={url-spring-data-rest-javadoc}
javadoc-location-com-fasterxml-jackson-annotation={url-jackson-annotations-javadoc}
javadoc-location-com-fasterxml-jackson-core={url-jackson-core-javadoc}
javadoc-location-com-fasterxml-jackson-databind={url-jackson-databind-javadoc}
javadoc-location-com-fasterxml-jackson-dataformat-xml={url-jackson-dataformat-xml-javadoc}
javadoc-location-tools-jackson-core={url-jackson-core-javadoc}
javadoc-location-tools-jackson-databind={url-jackson-databind-javadoc}
javadoc-location-tools-jackson-dataformat-xml={url-jackson-dataformat-xml-javadoc}
# === API References ===

View File

@ -280,12 +280,12 @@ class AntoraAsciidocAttributesTests {
addMockTestcontainersVersion(versions, "rabbitmq", version);
addMockTestcontainersVersion(versions, "redpanda", version);
addMockTestcontainersVersion(versions, "r2dbc", version);
addMockJacksonCoreVersion(versions, "jackson-annotations", version);
addMockJackson2CoreVersion(versions, "jackson-annotations", version);
addMockJacksonCoreVersion(versions, "jackson-core", version);
addMockJacksonCoreVersion(versions, "jackson-databind", version);
versions.put("org.apache.pulsar:pulsar-client-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;
}
@ -297,8 +297,12 @@ class AntoraAsciidocAttributesTests {
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);
}
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")
implementation("com.fasterxml.jackson.core:jackson-databind")
implementation("com.fasterxml.jackson.module:jackson-module-parameter-names")
implementation("net.java.dev.jna:jna-platform")
implementation("org.apache.commons:commons-compress")
implementation("org.apache.httpcomponents.client5:httpclient5")
implementation("org.springframework:spring-core")
implementation("org.tomlj:tomlj:1.0.0")
implementation("tools.jackson.core:jackson-databind")
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.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 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.ImageConfig;
@ -152,7 +152,7 @@ class BuilderMetadata extends MappedObject {
String json = SharedObjectMapper.get().writeValueAsString(getNode());
update.withLabel(LABEL_NAME, json);
}
catch (JsonProcessingException ex) {
catch (JacksonException ex) {
throw new IllegalStateException(ex);
}
}
@ -239,7 +239,7 @@ class BuilderMetadata extends MappedObject {
RunImage(JsonNode node) {
super(node, MethodHandles.lookup());
this.image = extractImage();
this.mirrors = childrenAt("/mirrors", JsonNode::asText);
this.mirrors = childrenAt("/mirrors", JsonNode::asString);
}
private String extractImage() {
@ -352,7 +352,7 @@ class BuilderMetadata extends MappedObject {
private final ObjectNode copy;
private Update(BuilderMetadata source) {
this.copy = source.getNode().deepCopy();
this.copy = (ObjectNode) source.getNode().deepCopy();
}
private BuilderMetadata run(Consumer<Update> update) {

View File

@ -21,8 +21,8 @@ import java.lang.invoke.MethodHandles;
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.databind.JsonNode;
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.ImageConfig;

View File

@ -19,8 +19,8 @@ package org.springframework.boot.buildpack.platform.build;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import com.fasterxml.jackson.databind.JsonNode;
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.ImageConfig;

View File

@ -17,6 +17,7 @@
package org.springframework.boot.buildpack.platform.docker;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
@ -286,11 +287,14 @@ public class DockerApi {
listener.onStart();
try {
try (Response response = http().post(loadUri, "application/x-tar", archive::writeTo)) {
jsonStream().get(response.getContent(), LoadImageUpdateEvent.class, (event) -> {
InputStream content = response.getContent();
if (content != null) {
jsonStream().get(content, LoadImageUpdateEvent.class, (event) -> {
streamListener.onUpdate(event);
listener.onUpdate(event);
});
}
}
streamListener.assertValidResponseReceived();
}
finally {
@ -395,7 +399,7 @@ public class DockerApi {
: buildUrl("/containers/create");
try (Response response = http().post(createUri, "application/json", config::writeTo)) {
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 com.fasterxml.jackson.databind.JsonNode;
import org.jspecify.annotations.Nullable;
import tools.jackson.databind.JsonNode;
import org.springframework.boot.buildpack.platform.json.MappedObject;
import org.springframework.util.Assert;

View File

@ -28,10 +28,10 @@ import java.util.HexFormat;
import java.util.Map;
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 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.SharedObjectMapper;
@ -120,7 +120,7 @@ final class DockerConfigurationMetadata {
try {
return DockerConfig.fromJson(readPathContent(path));
}
catch (JsonProcessingException ex) {
catch (JacksonException ex) {
throw new IllegalStateException("Error parsing Docker configuration file '" + path + "'", ex);
}
}
@ -142,7 +142,7 @@ final class DockerConfigurationMetadata {
}
return context;
}
catch (JsonProcessingException ex) {
catch (JacksonException ex) {
throw new IllegalStateException("Error parsing Docker context metadata file '" + metaPath + "'", ex);
}
}
@ -181,7 +181,7 @@ final class DockerConfigurationMetadata {
super(node, MethodHandles.lookup());
this.currentContext = valueAt("/currentContext", 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);
}
@ -201,7 +201,7 @@ final class DockerConfigurationMetadata {
return this.auths;
}
static DockerConfig fromJson(String json) throws JsonProcessingException {
static DockerConfig fromJson(String json) {
return new DockerConfig(SharedObjectMapper.get().readTree(json));
}
@ -285,7 +285,7 @@ final class DockerConfigurationMetadata {
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);
}

View File

@ -19,8 +19,8 @@ package org.springframework.boot.buildpack.platform.docker.configuration;
import java.util.Base64;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.jspecify.annotations.Nullable;
import tools.jackson.core.JacksonException;
import org.springframework.boot.buildpack.platform.json.SharedObjectMapper;
@ -44,7 +44,7 @@ class JsonEncodedDockerRegistryAuthentication implements DockerRegistryAuthentic
try {
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);
}
}

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.io.entity.AbstractHttpEntity;
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.IOConsumer;
@ -196,7 +197,7 @@ abstract class HttpClientTransport implements HttpTransport {
try {
return SharedObjectMapper.get().readValue(content, Errors.class);
}
catch (IOException ex) {
catch (JacksonException ex) {
return null;
}
}
@ -209,7 +210,7 @@ abstract class HttpClientTransport implements HttpTransport {
Message message = SharedObjectMapper.get().readValue(content, Message.class);
return (message.getMessage() != null) ? message : null;
}
catch (IOException ex) {
catch (JacksonException ex) {
return null;
}
}

View File

@ -18,7 +18,7 @@ package org.springframework.boot.buildpack.platform.docker.type;
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.util.Assert;

View File

@ -26,10 +26,10 @@ import java.util.List;
import java.util.Map;
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 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.util.Assert;
@ -51,7 +51,7 @@ public class ContainerConfig {
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,
List<String> securityOptions) throws IOException {
List<String> securityOptions) {
Assert.notNull(image, "'image' must not be null");
Assert.hasText(command, "'command' must not be empty");
ObjectMapper objectMapper = SharedObjectMapper.get();
@ -135,15 +135,10 @@ public class ContainerConfig {
private ContainerConfig run(Consumer<Update> update) {
update.accept(this);
try {
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,
this.env, this.networkMode, this.securityOptions);
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
/**
* Update the container config with a specific user.

View File

@ -20,9 +20,9 @@ import java.io.IOException;
import java.io.InputStream;
import java.lang.invoke.MethodHandles;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.NullNode;
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.util.Assert;

View File

@ -23,8 +23,8 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import com.fasterxml.jackson.databind.JsonNode;
import org.jspecify.annotations.Nullable;
import tools.jackson.databind.JsonNode;
import org.springframework.boot.buildpack.platform.json.MappedObject;
import org.springframework.util.StringUtils;
@ -54,7 +54,7 @@ public class Image extends MappedObject {
Image(JsonNode node) {
super(node, MethodHandles.lookup());
this.digests = childrenAt("/RepoDigests", JsonNode::asText);
this.digests = childrenAt("/RepoDigests", JsonNode::asString);
this.config = new ImageConfig(getNode().at("/Config"));
this.layers = extractLayers(valueAt("/RootFS/Layers", 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.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 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.IOConsumer;
@ -173,11 +173,11 @@ public class ImageArchive implements TarArchive {
private ObjectNode createConfig(List<LayerId> writtenLayers) {
ObjectNode config = this.objectMapper.createObjectNode();
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("Os", config.textNode(this.os));
config.set("Architecture", config.textNode(this.architecture));
config.set("Variant", config.textNode(this.variant));
config.set("Os", config.stringNode(this.os));
config.set("Architecture", config.stringNode(this.architecture));
config.set("Variant", config.stringNode(this.variant));
config.set("RootFS", createRootFs(writtenLayers));
return config;
}
@ -212,7 +212,7 @@ public class ImageArchive implements TarArchive {
private ArrayNode createManifest(String config, List<LayerId> writtenLayers) {
ArrayNode manifest = this.objectMapper.createArrayNode();
ObjectNode entry = manifest.addObject();
entry.set("Config", entry.textNode(config));
entry.set("Config", entry.stringNode(config));
entry.set("Layers", getManifestLayers(writtenLayers));
if (this.tag != null) {
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.util.List;
import com.fasterxml.jackson.databind.JsonNode;
import tools.jackson.databind.JsonNode;
import org.springframework.boot.buildpack.platform.json.MappedObject;
import org.springframework.util.Assert;

View File

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

View File

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

View File

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

View File

@ -20,12 +20,11 @@ import java.io.IOException;
import java.io.InputStream;
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 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.
@ -64,8 +63,7 @@ public class JsonStream {
* @throws IOException on IO error
*/
public <T> void get(InputStream content, Class<T> type, Consumer<T> consumer) throws IOException {
JsonFactory jsonFactory = this.objectMapper.getFactory();
try (JsonParser parser = jsonFactory.createParser(content)) {
try (JsonParser parser = this.objectMapper.createParser(content)) {
while (!parser.isClosed()) {
JsonToken token = parser.nextToken();
if (token != null && token != JsonToken.END_OBJECT) {
@ -79,9 +77,9 @@ public class JsonStream {
}
@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)) {
ObjectNode node = this.objectMapper.readTree(parser);
ObjectNode node = (ObjectNode) this.objectMapper.readTree(parser);
if (node == null || node.isMissingNode() || node.isEmpty()) {
return null;
}

View File

@ -30,9 +30,10 @@ import java.util.List;
import java.util.Map;
import java.util.function.Function;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
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.StreamUtils;
@ -111,7 +112,7 @@ public class MappedObject {
return Collections.emptyList();
}
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);
}
@ -146,7 +147,7 @@ public class MappedObject {
try {
return SharedObjectMapper.get().treeToValue(result, type);
}
catch (IOException ex) {
catch (JacksonException ex) {
throw new IllegalStateException(ex);
}
}

View File

@ -16,11 +16,12 @@
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 tools.jackson.core.json.JsonWriteFeature;
import tools.jackson.databind.DeserializationFeature;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.PropertyNamingStrategies;
import tools.jackson.databind.SerializationFeature;
import tools.jackson.databind.json.JsonMapper;
/**
* Provides access to a shared pre-configured {@link ObjectMapper}.
@ -33,12 +34,12 @@ public final class SharedObjectMapper {
private static final ObjectMapper INSTANCE;
static {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new ParameterNamesModule());
objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setPropertyNamingStrategy(PropertyNamingStrategies.LOWER_CAMEL_CASE);
INSTANCE = objectMapper;
INSTANCE = JsonMapper.builder()
.enable(SerializationFeature.INDENT_OUTPUT)
.disable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS)
.disable(JsonWriteFeature.ESCAPE_FORWARD_SLASHES)
.propertyNamingStrategy(PropertyNamingStrategies.LOWER_CAMEL_CASE)
.build();
}
private SharedObjectMapper() {

View File

@ -26,9 +26,6 @@ import java.util.LinkedHashMap;
import java.util.List;
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 org.json.JSONException;
import org.json.JSONObject;
@ -37,6 +34,8 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.mockito.stubbing.Answer;
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.ContainerApi;
@ -482,7 +481,7 @@ class LifecycleTests {
return (invocation) -> {
ContainerConfig config = invocation.getArgument(0, ContainerConfig.class);
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);
if (invocation.getArguments().length > 2) {
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());
return (ArrayNode) node.at("/Cmd");
}

View File

@ -24,10 +24,10 @@ import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import com.fasterxml.jackson.core.type.TypeReference;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
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.json.SharedObjectMapper;

View File

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

View File

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

View File

@ -16,7 +16,6 @@
package org.springframework.boot.buildpack.platform.docker.type;
import java.io.IOException;
import java.util.Map;
import org.junit.jupiter.api.Test;
@ -35,7 +34,7 @@ import static org.assertj.core.api.Assertions.entry;
class ImageConfigTests extends AbstractJsonTests {
@Test
void getEnvContainsParsedValues() throws Exception {
void getEnvContainsParsedValues() {
ImageConfig imageConfig = getImageConfig();
Map<String, String> env = imageConfig.getEnv();
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
void whenConfigHasNoEnvThenImageConfigEnvIsEmpty() throws Exception {
void whenConfigHasNoEnvThenImageConfigEnvIsEmpty() {
ImageConfig imageConfig = getMinimalImageConfig();
Map<String, String> env = imageConfig.getEnv();
assertThat(env).isEmpty();
}
@Test
void whenConfigHasNoLabelsThenImageConfigLabelsIsEmpty() throws Exception {
void whenConfigHasNoLabelsThenImageConfigLabelsIsEmpty() {
ImageConfig imageConfig = getMinimalImageConfig();
Map<String, String> env = imageConfig.getLabels();
assertThat(env).isEmpty();
}
@Test
void getLabelsReturnsLabels() throws Exception {
void getLabelsReturnsLabels() {
ImageConfig imageConfig = getImageConfig();
Map<String, String> labels = imageConfig.getLabels();
assertThat(labels).hasSize(4).contains(entry("io.buildpacks.stack.id", "org.cloudfoundry.stacks.cflinuxfs3"));
}
@Test
void updateWithLabelUpdatesLabels() throws Exception {
void updateWithLabelUpdatesLabels() {
ImageConfig imageConfig = getImageConfig();
ImageConfig updatedImageConfig = imageConfig
.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"));
}
private ImageConfig getImageConfig() throws IOException {
private ImageConfig getImageConfig() {
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")));
}

View File

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

View File

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

View File

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

View File

@ -20,8 +20,8 @@ import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.junit.jupiter.api.Test;
import tools.jackson.databind.node.ObjectNode;
import static org.assertj.core.api.Assertions.assertThat;
@ -44,7 +44,7 @@ class JsonStreamTests extends AbstractJsonTests {
List<ObjectNode> result = new ArrayList<>();
this.jsonStream.get(getContent("stream.json"), result::add);
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");
}

View File

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

View File

@ -16,12 +16,11 @@
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 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;
@ -36,14 +35,14 @@ class SharedObjectMapperTests {
void getReturnsConfiguredObjectMapper() {
ObjectMapper mapper = SharedObjectMapper.get();
assertThat(mapper).isNotNull();
assertThat(mapper.getRegisteredModuleIds()).contains(new ParameterNamesModule().getTypeId());
assertThat(SerializationFeature.INDENT_OUTPUT
.enabledIn(mapper.getSerializationConfig().getSerializationFeatures())).isTrue();
assertThat(
SerializationFeature.INDENT_OUTPUT.enabledIn(mapper.serializationConfig().getSerializationFeatures()))
.isTrue();
assertThat(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
.enabledIn(mapper.getDeserializationConfig().getDeserializationFeatures())).isFalse();
assertThat(mapper.getSerializationConfig().getPropertyNamingStrategy())
.enabledIn(mapper.deserializationConfig().getDeserializationFeatures())).isFalse();
assertThat(mapper.serializationConfig().getPropertyNamingStrategy())
.isEqualTo(PropertyNamingStrategies.LOWER_CAMEL_CASE);
assertThat(mapper.getDeserializationConfig().getPropertyNamingStrategy())
assertThat(mapper.deserializationConfig().getPropertyNamingStrategy())
.isEqualTo(PropertyNamingStrategies.LOWER_CAMEL_CASE);
}

View File

@ -45,10 +45,10 @@ dependencies {
testImplementation(project(":test-support:spring-boot-test-support"))
testImplementation(testFixtures(project(":core:spring-boot")))
testImplementation("ch.qos.logback:logback-classic")
testImplementation("com.fasterxml.jackson.core:jackson-databind")
testImplementation("io.projectreactor:reactor-core")
testImplementation("org.springframework:spring-context-support")
testImplementation("org.springframework.security:spring-security-config")
testImplementation("tools.jackson.core:jackson-databind")
testRuntimeOnly("com.github.ben-manes.caffeine:caffeine")
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.beans.factory.config.BeanDefinition;
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.testsupport.classpath.ClassPathExclusions;
@ -40,7 +42,8 @@ class NonAspectJAopAutoConfigurationTests {
@Test
void whenAspectJIsAbsentAndProxyTargetClassIsEnabledProxyCreatorBeanIsDefined() {
this.contextRunner.run((context) -> {
this.contextRunner.withInitializer(ConditionEvaluationReportLoggingListener.forLogLevel(LogLevel.INFO))
.run((context) -> {
BeanDefinition defaultProxyConfig = context.getBeanFactory()
.getBeanDefinition(AutoProxyUtils.DEFAULT_PROXY_CONFIG_BEAN_NAME);
assertThat(defaultProxyConfig.getPropertyValues().get("proxyTargetClass")).isEqualTo(Boolean.TRUE);
@ -49,7 +52,8 @@ class NonAspectJAopAutoConfigurationTests {
@Test
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)
.doesNotHaveBean(AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME));
}

View File

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

View File

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

View File

@ -16,16 +16,14 @@
package org.springframework.boot.docker.compose.core;
import java.io.IOException;
import java.util.List;
import java.util.Locale;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import tools.jackson.databind.DeserializationFeature;
import tools.jackson.databind.JavaType;
import tools.jackson.databind.MapperFeature;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.json.JsonMapper;
/**
* Support class used to handle JSON returned from the {@link DockerCli}.
@ -40,7 +38,6 @@ final class DockerJson {
.defaultLocale(Locale.ENGLISH)
.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.addModule(new ParameterNamesModule())
.build();
private DockerJson() {
@ -74,12 +71,7 @@ final class DockerJson {
}
private static <T> T deserialize(String json, JavaType type) {
try {
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:8080/library/redis"));
assertThat(predicateOf("redis")).rejects(sourceOf("internalhost:8080/redis"));
assertThat(predicateOf("redis")).accepts(sourceOf("docker.my-company.com/library/redis:latest"));
}
@Test

View File

@ -27,7 +27,6 @@ dependencies {
api(project(":core:spring-boot"))
api("org.springframework:spring-test")
optional("com.fasterxml.jackson.core:jackson-databind")
optional("com.google.code.gson:gson")
optional("com.jayway.jsonpath:json-path")
optional("io.projectreactor.netty:reactor-netty-http")
@ -44,6 +43,7 @@ dependencies {
optional("org.springframework:spring-web")
optional("org.springframework:spring-webflux")
optional("org.springframework.graphql:spring-graphql-test")
optional("tools.jackson.core:jackson-databind")
testImplementation(project(":test-support:spring-boot-test-support"))
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.InputStream;
import java.io.InputStreamReader;
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.spi.json.JacksonJsonProvider;
import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider;
import com.jayway.jsonpath.InvalidJsonException;
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 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.core.ResolvableType;
@ -35,14 +48,14 @@ import org.springframework.util.Assert;
/**
* 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 {
*
* private JacksonTester&lt;ExampleObject&gt; json;
*
* &#064;Before
* public void setup() {
* ObjectMapper objectMapper = new ObjectMapper();
* JsonMapper jsonMapper = new JsonMapper();
* JacksonTester.initFields(this, objectMapper);
* }
*
@ -65,42 +78,44 @@ import org.springframework.util.Assert;
*/
public class JacksonTester<T> extends AbstractJsonMarshalTester<T> {
private final ObjectMapper objectMapper;
private final JsonMapper jsonMapper;
private @Nullable Class<?> view;
/**
* 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) {
Assert.notNull(objectMapper, "'objectMapper' must not be null");
this.objectMapper = objectMapper;
protected JacksonTester(JsonMapper jsonMapper) {
Assert.notNull(jsonMapper, "'objectMapper' must not be null");
this.jsonMapper = jsonMapper;
}
/**
* Create a new {@link JacksonTester} instance.
* @param resourceLoadClass the source class used to load resources
* @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) {
this(resourceLoadClass, type, objectMapper, null);
public JacksonTester(Class<?> resourceLoadClass, ResolvableType type, JsonMapper jsonMapper) {
this(resourceLoadClass, type, jsonMapper, null);
}
public JacksonTester(Class<?> resourceLoadClass, ResolvableType type, ObjectMapper objectMapper,
public JacksonTester(Class<?> resourceLoadClass, ResolvableType type, JsonMapper jsonMapper,
@Nullable Class<?> view) {
super(resourceLoadClass, type);
Assert.notNull(objectMapper, "'objectMapper' must not be null");
this.objectMapper = objectMapper;
Assert.notNull(jsonMapper, "'jsonMapper' must not be null");
this.jsonMapper = jsonMapper;
this.view = view;
}
@Override
protected JsonContent<T> getJsonContent(String json) {
Configuration configuration = Configuration.builder()
.jsonProvider(new JacksonJsonProvider(this.objectMapper))
.mappingProvider(new JacksonMappingProvider(this.objectMapper))
.jsonProvider(new JacksonJsonProvider(this.jsonMapper))
.mappingProvider(new JacksonMappingProvider(this.jsonMapper))
.build();
Class<?> resourceLoadClass = getResourceLoadClass();
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) {
ObjectReader objectReader = this.objectMapper.readerFor(getType(type));
ObjectReader objectReader = this.jsonMapper.readerFor(getType(type));
if (this.view != null) {
return objectReader.withView(this.view);
}
@ -131,7 +146,7 @@ public class JacksonTester<T> extends AbstractJsonMarshalTester<T> {
}
private ObjectWriter getObjectWriter(ResolvableType type) {
ObjectWriter objectWriter = this.objectMapper.writerFor(getType(type));
ObjectWriter objectWriter = this.jsonMapper.writerFor(getType(type));
if (this.view != null) {
return objectWriter.withView(this.view);
}
@ -139,29 +154,31 @@ public class JacksonTester<T> extends AbstractJsonMarshalTester<T> {
}
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
* class-level documentation} for example usage.
* @param testInstance the test instance
* @param objectMapper the object mapper
* @see #initFields(Object, ObjectMapper)
* @param jsonMapper the JSON mapper
* @since 4.0.0
* @see #initFields(Object, JsonMapper)
*/
public static void initFields(Object testInstance, ObjectMapper objectMapper) {
new JacksonFieldInitializer().initFields(testInstance, objectMapper);
public static void initFields(Object testInstance, JsonMapper jsonMapper) {
new JacksonFieldInitializer().initFields(testInstance, jsonMapper);
}
/**
* Utility method to initialize {@link JacksonTester} fields. See {@link JacksonTester
* class-level documentation} for example usage.
* @param testInstance the test instance
* @param objectMapperFactory a factory to create the object mapper
* @see #initFields(Object, ObjectMapper)
* @param jsonMapperFactory a factory to create the JSON mapper
* @since 4.0.0
* @see #initFields(Object, JsonMapper)
*/
public static void initFields(Object testInstance, ObjectFactory<ObjectMapper> objectMapperFactory) {
new JacksonFieldInitializer().initFields(testInstance, objectMapperFactory);
public static void initFields(Object testInstance, ObjectFactory<JsonMapper> jsonMapperFactory) {
new JacksonFieldInitializer().initFields(testInstance, jsonMapperFactory);
}
/**
@ -175,13 +192,13 @@ public class JacksonTester<T> extends AbstractJsonMarshalTester<T> {
ResolvableType type = getType();
Assert.state(resourceLoadClass != null, "'resourceLoadClass' 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.
*/
private static class JacksonFieldInitializer extends FieldInitializer<ObjectMapper> {
private static class JacksonFieldInitializer extends FieldInitializer<JsonMapper> {
protected JacksonFieldInitializer() {
super(JacksonTester.class);
@ -189,10 +206,115 @@ public class JacksonTester<T> extends AbstractJsonMarshalTester<T> {
@Override
protected AbstractJsonMarshalTester<Object> createTester(Class<?> resourceLoadClass, ResolvableType type,
ObjectMapper marshaller) {
JsonMapper 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.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.junit.jupiter.api.Test;
import tools.jackson.databind.MapperFeature;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.core.io.ByteArrayResource;
@ -55,14 +54,14 @@ class JacksonTesterIntegrationTests {
@Test
void typicalTest() throws Exception {
JacksonTester.initFields(this, new ObjectMapper());
JacksonTester.initFields(this, new JsonMapper());
String example = JSON;
assertThat(this.simpleJson.parse(example).getObject().getName()).isEqualTo("Spring");
}
@Test
void typicalListTest() throws Exception {
JacksonTester.initFields(this, new ObjectMapper());
JacksonTester.initFields(this, new JsonMapper());
String example = "[" + JSON + "]";
assertThat(this.listJson.parse(example)).asInstanceOf(InstanceOfAssertFactories.LIST).hasSize(1);
assertThat(this.listJson.parse(example).getObject().get(0).getName()).isEqualTo("Spring");
@ -70,7 +69,7 @@ class JacksonTesterIntegrationTests {
@Test
void typicalMapTest() throws Exception {
JacksonTester.initFields(this, new ObjectMapper());
JacksonTester.initFields(this, new JsonMapper());
Map<String, Integer> map = new LinkedHashMap<>();
map.put("a", 1);
map.put("b", 2);
@ -79,7 +78,7 @@ class JacksonTesterIntegrationTests {
@Test
void stringLiteral() throws Exception {
JacksonTester.initFields(this, new ObjectMapper());
JacksonTester.initFields(this, new JsonMapper());
String stringWithSpecialCharacters = "myString";
assertThat(this.stringJson.write(stringWithSpecialCharacters)).extractingJsonPathStringValue("@")
.isEqualTo(stringWithSpecialCharacters);
@ -87,7 +86,7 @@ class JacksonTesterIntegrationTests {
@Test
void parseSpecialCharactersTest() throws Exception {
JacksonTester.initFields(this, new ObjectMapper());
JacksonTester.initFields(this, new JsonMapper());
// Confirms that the handling of special characters is symmetrical between
// the serialization (through the JacksonTester) and the parsing (through
// 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 com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.core.ResolvableType;
@ -35,14 +35,14 @@ class JacksonTesterTests extends AbstractJsonMarshalTesterTests {
@Test
void initFieldsWhenTestIsNullShouldThrowException() {
assertThatIllegalArgumentException().isThrownBy(() -> JacksonTester.initFields(null, new ObjectMapper()))
assertThatIllegalArgumentException().isThrownBy(() -> JacksonTester.initFields(null, new JsonMapper()))
.withMessageContaining("'testInstance' must not be null");
}
@Test
void initFieldsWhenMarshallerIsNullShouldThrowException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> JacksonTester.initFields(new InitFieldsTestClass(), (ObjectMapper) null))
.isThrownBy(() -> JacksonTester.initFields(new InitFieldsTestClass(), (JsonMapper) null))
.withMessageContaining("'marshaller' must not be null");
}
@ -51,7 +51,7 @@ class JacksonTesterTests extends AbstractJsonMarshalTesterTests {
InitFieldsTestClass test = new InitFieldsTestClass();
assertThat(test.test).isNull();
assertThat(test.base).isNull();
JacksonTester.initFields(test, new ObjectMapper());
JacksonTester.initFields(test, new JsonMapper());
assertThat(test.test).isNotNull();
assertThat(test.base).isNotNull();
assertThat(test.test.getType().resolve()).isEqualTo(List.class);
@ -60,7 +60,7 @@ class JacksonTesterTests extends AbstractJsonMarshalTesterTests {
@Override
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 {
@ -68,7 +68,7 @@ class JacksonTesterTests extends AbstractJsonMarshalTesterTests {
public JacksonTester<ExampleObject> base;
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<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")
optional("ch.qos.logback:logback-classic")
optional("com.fasterxml.jackson.core:jackson-databind")
optional("com.google.code.gson:gson")
optional("io.projectreactor:reactor-core")
optional("jakarta.servlet:jakarta.servlet-api")
@ -49,6 +48,7 @@ dependencies {
optional("org.springframework:spring-test")
optional("org.springframework:spring-web")
optional("org.yaml:snakeyaml")
optional("tools.jackson.core:jackson-databind")
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.Map;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
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}.

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}
*/
public static JsonParser getJsonParser() {
if (ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", null)) {
if (ClassUtils.isPresent("tools.jackson.databind.ObjectMapper", null)) {
return new JacksonJsonParser();
}
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.ConfigDataPropertiesRuntimeHints,\
org.springframework.boot.env.PropertySourceRuntimeHints,\
org.springframework.boot.json.JacksonRuntimeHints,\
org.springframework.boot.logging.java.JavaLoggingSystemRuntimeHints,\
org.springframework.boot.logging.logback.LogbackRuntimeHints,\
org.springframework.boot.logging.structured.ElasticCommonSchemaProperties$ElasticCommonSchemaPropertiesRuntimeHints,\

View File

@ -16,12 +16,10 @@
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.Test;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.ObjectMapper;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
@ -43,7 +41,7 @@ class JacksonJsonParserTests extends AbstractJsonParserTests {
@Test
@SuppressWarnings("unchecked")
void instanceWithSpecificObjectMapper() throws IOException {
void instanceWithSpecificObjectMapper() {
ObjectMapper objectMapper = spy(new ObjectMapper());
new JacksonJsonParser(objectMapper).parseMap("{}");
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.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.core.impl.MutableLogEvent;
import org.apache.logging.log4j.message.SimpleMessage;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
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.StructuredLoggingJsonMembersCustomizer;
@ -79,14 +77,8 @@ abstract class AbstractStructuredLoggingTests {
}
protected Map<String, Object> deserialize(String json) {
try {
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.stream.Stream;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.logging.log4j.LogManager;
@ -333,15 +332,16 @@ class Log4J2LoggingSystemTests extends AbstractLoggingSystemTests {
// No classes, only XML
Arguments.of(Collections.emptyList(), List.of(".xml")),
// Log4j Core 2
Arguments.of(List.of(JsonConfigurationFactory.class.getName(), ObjectMapper.class.getName()),
List.of(".json", ".jsn", ".xml")),
Arguments.of(List.of(JsonConfigurationFactory.class.getName(),
"com.fasterxml.jackson.databind.ObjectMapper"), List.of(".json", ".jsn", ".xml")),
Arguments.of(List.of(PropertiesConfigurationFactory.class.getName(),
PropertiesConfigurationBuilder.class.getName()), List.of(".properties", ".xml")),
Arguments.of(List.of(YamlConfigurationFactory.class.getName(),
"com.fasterxml.jackson.dataformat.yaml.YAMLMapper"), List.of(".yaml", ".yml", ".xml")),
Arguments.of(List.of(JsonConfigurationFactory.class.getName(), ObjectMapper.class.getName(),
PropertiesConfigurationFactory.class.getName(), PropertiesConfigurationBuilder.class.getName(),
YamlConfigurationFactory.class.getName(), "com.fasterxml.jackson.dataformat.yaml.YAMLMapper"),
Arguments.of(List.of(JsonConfigurationFactory.class.getName(),
"com.fasterxml.jackson.databind.ObjectMapper", PropertiesConfigurationFactory.class.getName(),
PropertiesConfigurationBuilder.class.getName(), YamlConfigurationFactory.class.getName(),
"com.fasterxml.jackson.dataformat.yaml.YAMLMapper"),
List.of(".properties", ".yaml", ".yml", ".json", ".jsn", ".xml")),
// Log4j Core 3
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.spi.LoggingEvent;
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.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
@ -38,6 +34,8 @@ import org.mockito.junit.jupiter.MockitoExtension;
import org.slf4j.Marker;
import org.slf4j.event.KeyValuePair;
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.StructuredLoggingJsonMembersCustomizer;
@ -122,14 +120,8 @@ abstract class AbstractStructuredLoggingTests {
}
protected Map<String, Object> deserialize(String json) {
try {
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 com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import tools.jackson.databind.ObjectMapper;
import org.springframework.context.MessageSourceResolvable;
import org.springframework.context.support.DefaultMessageSourceResolvable;
@ -46,7 +45,7 @@ class ErrorTests {
}
@Test
void errorCauseDoesNotAppearInJson() throws JsonProcessingException {
void errorCauseDoesNotAppearInJson() {
String json = new ObjectMapper()
.writeValueAsString(Error.wrapIfNecessary(List.of(new CustomMessageSourceResolvable("code"))));
assertThat(json).doesNotContain("some detail");

View File

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

View File

@ -16,7 +16,6 @@
package org.springframework.boot.actuate.docs.env;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@ -24,9 +23,10 @@ import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
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.endpoint.Show;
@ -116,8 +116,7 @@ class EnvironmentEndpointDocumentationTests extends MockMvcEndpointDocumentation
@SuppressWarnings("unchecked")
private byte[] filterProperties(byte[] content, MediaType mediaType) {
ObjectMapper objectMapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);
try {
ObjectMapper objectMapper = JsonMapper.builder().enable(SerializationFeature.INDENT_OUTPUT).build();
Map<String, Object> payload = objectMapper.readValue(content, Map.class);
List<Map<String, Object>> propertySources = (List<Map<String, Object>>) payload.get("propertySources");
for (Map<String, Object> propertySource : propertySources) {
@ -131,10 +130,6 @@ class EnvironmentEndpointDocumentationTests extends MockMvcEndpointDocumentation
}
return objectMapper.writeValueAsBytes(payload);
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
private boolean retainKey(String key) {
return key.startsWith("java.") || key.equals("JAVA_HOME") || key.startsWith("com.example.");

View File

@ -34,7 +34,6 @@ import org.springframework.restdocs.payload.FieldDescriptor;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.MapSession;
import org.springframework.session.Session;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import static org.assertj.core.api.Assertions.assertThat;
@ -51,7 +50,6 @@ import static org.springframework.restdocs.request.RequestDocumentation.queryPar
*
* @author Andy Wilkinson
*/
@TestPropertySource(properties = "spring.jackson.serialization.write-dates-as-timestamps=false")
class SessionsEndpointDocumentationTests extends MockMvcEndpointDocumentationTests {
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[#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-objectmapper[#howto.spring-mvc.customize-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-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.spring-mvc.customize-responsebody-rendering]
* 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]]
== 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[]
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.
@ -29,7 +29,7 @@ To use the Jackson XML renderer, add the following dependency to your project:
[source,xml]
----
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<groupId>tools.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</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]]
== Customize the Jackson ObjectMapper
[[howto.spring-mvc.customize-jackson-jsonmapper]]
== 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.
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.
You can configure the javadoc:tools.jackson.databind.JsonMapper[] by using the environment.
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:
|===
| Enum | Property | Values
| javadoc:com.fasterxml.jackson.databind.cfg.EnumFeature[]
| javadoc:tools.jackson.databind.cfg.EnumFeature[]
| `spring.jackson.datatype.enum.<feature_name>`
| `true`, `false`
| javadoc:com.fasterxml.jackson.databind.cfg.JsonNodeFeature[]
| javadoc:tools.jackson.databind.cfg.JsonNodeFeature[]
| `spring.jackson.datatype.json-node.<feature_name>`
| `true`, `false`
| javadoc:com.fasterxml.jackson.databind.DeserializationFeature[]
| javadoc:tools.jackson.databind.DeserializationFeature[]
| `spring.jackson.deserialization.<feature_name>`
| `true`, `false`
| javadoc:com.fasterxml.jackson.core.JsonGenerator$Feature[]
| `spring.jackson.generator.<feature_name>`
| `true`, `false`
| javadoc:com.fasterxml.jackson.databind.MapperFeature[]
| javadoc:tools.jackson.databind.MapperFeature[]
| `spring.jackson.mapper.<feature_name>`
| `true`, `false`
| javadoc:com.fasterxml.jackson.core.JsonParser$Feature[]
| `spring.jackson.parser.<feature_name>`
| javadoc:tools.jackson.core.JsonReadFeature[]
| `spring.jackson.read.<feature_name>`
| `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>`
| `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`.
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.
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.
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].
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.
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.
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.
@ -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]).
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.
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:
- Gson
- Jackson
- Jackson 3
- JSON-B
Jackson is the preferred and default library.
@ -15,18 +15,18 @@ Jackson is the preferred and default library.
== Jackson
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.
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[]].
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-jsonmapper[customizing the configuration of the javadoc:tools.jackson.databind.json.JsonMapper[]].
[[features.json.jackson.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.
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:
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.
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.
See javadoc:org.springframework.boot.jackson.JsonObjectSerializer[] and javadoc:org.springframework.boot.jackson.JsonObjectDeserializer[] in the API documentation for details.
Spring Boot also provides javadoc:org.springframework.boot.jackson.ObjectValueSerializer[] and javadoc:org.springframework.boot.jackson.ObjectValueDeserializer[] base classes that provide useful alternatives to the standard Jackson versions when serializing objects.
See javadoc:org.springframework.boot.jackson.ObjectValueSerializer[] and javadoc:org.springframework.boot.jackson.ObjectValueDeserializer[] in the API documentation for details.
The example above can be rewritten to use javadoc:org.springframework.boot.jackson.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[]
@ -47,7 +47,7 @@ include-code::object/MyJsonComponent[]
=== Mixins
Jackson has support for mixins that can be used to mix additional annotations into those already declared on a target class.
Spring Boot's Jackson auto-configuration will scan your application's packages for classes annotated with javadoc:org.springframework.boot.jackson.JsonMixin[format=annotation] and register them with the auto-configured javadoc: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[].

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.
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`
* `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: 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.
@ -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: 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.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -43,4 +43,5 @@ dependencies {
testImplementation("net.minidev:json-smart")
testImplementation("org.springframework.security:spring-security-web")
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;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.MockClock;
import io.micrometer.core.instrument.binder.jvm.JvmMemoryMetrics;
import io.micrometer.core.instrument.simple.SimpleConfig;
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.micrometer.metrics.actuate.endpoint.MetricsEndpoint;
@ -49,7 +48,7 @@ class MetricsEndpointWebIntegrationTests {
@WebEndpointTest
@SuppressWarnings("unchecked")
void listNames(WebTestClient client) throws IOException {
void listNames(WebTestClient client) {
String responseBody = client.get()
.uri("/actuator/metrics")
.exchange()

View File

@ -16,6 +16,12 @@
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 org.apache.activemq.ActiveMQConnectionFactory;
import org.junit.jupiter.api.Test;
@ -47,6 +53,14 @@ class ActiveMQAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.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
void brokerIsEmbeddedByDefault() {
this.contextRunner.withUserConfiguration(EmptyConfiguration.class).run((context) -> {

View File

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

View File

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

View File

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

View File

@ -22,10 +22,10 @@ import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
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.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
@ -44,49 +44,49 @@ class JacksonEndpointAutoConfigurationTests {
.withConfiguration(AutoConfigurations.of(JacksonEndpointAutoConfiguration.class));
@Test
void endpointObjectMapperWhenNoProperty() {
this.runner.run((context) -> assertThat(context).hasSingleBean(EndpointObjectMapper.class));
void endpointJsonMapperWhenNoProperty() {
this.runner.run((context) -> assertThat(context).hasSingleBean(EndpointJsonMapper.class));
}
@Test
void endpointObjectMapperWhenPropertyTrue() {
void endpointJsonMapperWhenPropertyTrue() {
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
void endpointObjectMapperWhenPropertyFalse() {
void endpointJsonMapperWhenPropertyFalse() {
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
void endpointObjectMapperDoesNotSerializeDatesAsTimestamps() {
void endpointJsonMapperDoesNotSerializeDatesAsTimestamps() {
this.runner.run((context) -> {
ObjectMapper objectMapper = context.getBean(EndpointObjectMapper.class).get();
JsonMapper jsonMapper = context.getBean(EndpointJsonMapper.class).get();
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));
});
}
@Test
void endpointObjectMapperDoesNotSerializeDurationsAsTimestamps() {
void endpointJsonMapperDoesNotSerializeDurationsAsTimestamps() {
this.runner.run((context) -> {
ObjectMapper objectMapper = context.getBean(EndpointObjectMapper.class).get();
JsonMapper jsonMapper = context.getBean(EndpointJsonMapper.class).get();
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());
});
}
@Test
void endpointObjectMapperDoesNotSerializeNullValues() {
void endpointJsonMapperDoesNotSerializeNullValues() {
this.runner.run((context) -> {
ObjectMapper objectMapper = context.getBean(EndpointObjectMapper.class).get();
JsonMapper jsonMapper = context.getBean(EndpointJsonMapper.class).get();
HashMap<String, String> map = new HashMap<>();
map.put("key", null);
String json = objectMapper.writeValueAsString(map);
String json = jsonMapper.writeValueAsString(map);
assertThat(json).isEqualTo("{}");
});
}
@ -95,16 +95,16 @@ class JacksonEndpointAutoConfigurationTests {
static class TestEndpointMapperConfiguration {
@Bean
TestEndpointObjectMapper testEndpointObjectMapper() {
return new TestEndpointObjectMapper();
TestEndpointJsonMapper testEndpointJsonMapper() {
return new TestEndpointJsonMapper();
}
}
static class TestEndpointObjectMapper implements EndpointObjectMapper {
static class TestEndpointJsonMapper implements EndpointJsonMapper {
@Override
public ObjectMapper get() {
public JsonMapper get() {
return null;
}

View File

@ -32,8 +32,6 @@ dependencies {
optional(project(":module:spring-boot-jsonb"))
optional(project(":module:spring-boot-validation"))
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.google.code.findbugs:jsr305")
optional("com.zaxxer:HikariCP")
@ -57,8 +55,8 @@ dependencies {
optional("org.springframework.graphql:spring-graphql")
optional("org.springframework.security:spring-security-core")
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.springframework:spring-test")
testFixturesImplementation("org.springframework:spring-webflux")

View File

@ -29,30 +29,30 @@ import java.util.function.Predicate;
import java.util.stream.Collectors;
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.LogFactory;
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.boot.actuate.endpoint.OperationResponseBody;
@ -180,13 +180,10 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
*/
protected void configureJsonMapper(JsonMapper.Builder builder) {
builder.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
builder.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
builder.configure(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS, false);
builder.configure(MapperFeature.USE_STD_BEAN_NAMING, true);
builder.serializationInclusion(Include.NON_NULL);
builder.changeDefaultPropertyInclusion((value) -> value.withValueInclusion(Include.NON_NULL));
builder.accessorNaming(new DefaultAccessorNamingStrategy.Provider().withFirstCharAcceptance(true, false));
applyConfigurationPropertiesFilter(builder);
applySerializationModifier(builder);
builder.addModule(new JavaTimeModule());
builder.addModule(new ConfigurationPropertiesModule());
}
@ -411,8 +408,8 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
private static final class ConfigurationPropertiesAnnotationIntrospector extends JacksonAnnotationIntrospector {
@Override
public Object findFilterId(Annotated a) {
Object id = super.findFilterId(a);
public Object findFilterId(MapperConfig<?> config, Annotated a) {
Object id = super.findFilterId(config, a);
if (id == null) {
id = CONFIGURATION_PROPERTIES_FILTER_ID;
}
@ -450,7 +447,7 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
}
@Override
public void serializeAsField(Object pojo, JsonGenerator jgen, SerializerProvider provider,
public void serializeAsProperty(Object pojo, JsonGenerator jgen, SerializationContext context,
PropertyWriter writer) throws Exception {
if (writer instanceof BeanPropertyWriter beanPropertyWriter) {
try {
@ -470,7 +467,7 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
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();
@Override
public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc,
public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription.Supplier beanDesc,
List<BeanPropertyWriter> beanProperties) {
List<BeanPropertyWriter> result = new ArrayList<>();
Class<?> beanClass = beanDesc.getType().getRawClass();
@ -508,7 +505,7 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
return result;
}
private boolean isCandidate(BeanDescription beanDesc, BeanPropertyWriter writer,
private boolean isCandidate(BeanDescription.Supplier beanDesc, BeanPropertyWriter writer,
@Nullable Constructor<?> constructor) {
if (constructor != null) {
Parameter[] parameters = constructor.getParameters();
@ -529,10 +526,10 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
return isReadable(beanDesc, writer);
}
private boolean isReadable(BeanDescription beanDesc, BeanPropertyWriter writer) {
Class<?> parentType = beanDesc.getType().getRawClass();
private boolean isReadable(BeanDescription.Supplier beanDesc, BeanPropertyWriter writer) {
Class<?> parentType = beanDesc.get().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,
// 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

View File

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

View File

@ -18,19 +18,19 @@ package org.springframework.boot.actuate.endpoint.jackson;
import java.util.Set;
import com.fasterxml.jackson.databind.ObjectMapper;
import tools.jackson.databind.json.JsonMapper;
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.
*
* @author Phillip Webb
* @since 3.0.0
* @since 4.0.0
* @see OperationResponseBody
*/
public interface EndpointObjectMapper {
public interface EndpointJsonMapper {
/**
* The default supported types.
@ -38,16 +38,15 @@ public interface EndpointObjectMapper {
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.
* @return the object mapper
*/
ObjectMapper get();
JsonMapper get();
/**
* Return the types that this endpoint mapper supports.
* @return the supported types
* @since 4.0.0
*/
default Set<Class<?>> getSupportedTypes() {
return DEFAULT_SUPPORTED_TYPES;

View File

@ -20,9 +20,9 @@ import java.util.Collection;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
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

View File

@ -20,6 +20,7 @@ import java.util.Collections;
import org.json.JSONObject;
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.assertThatIllegalArgumentException;
@ -69,13 +70,10 @@ class AuditEventTests {
}
@Test
@SuppressWarnings({ "removal", "deprecation" })
void jsonFormat() throws Exception {
AuditEvent event = new AuditEvent("johannes", "UNKNOWN",
Collections.singletonMap("type", (Object) "BadCredentials"));
String json = org.springframework.http.converter.json.Jackson2ObjectMapperBuilder.json()
.build()
.writeValueAsString(event);
String json = new ObjectMapper().writeValueAsString(event);
JSONObject jsonObject = new JSONObject(json);
assertThat(jsonObject.getString("type")).isEqualTo("UNKNOWN");
assertThat(jsonObject.getJSONObject("data").getString("type")).isEqualTo("BadCredentials");

View File

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

View File

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

View File

@ -20,10 +20,10 @@ import java.util.Collections;
import java.util.LinkedHashMap;
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 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.health.contributor.Health;

View File

@ -16,10 +16,10 @@
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 tools.jackson.databind.MapperFeature;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.boot.health.contributor.Health;

View File

@ -22,10 +22,10 @@ import java.util.LinkedHashSet;
import java.util.Map;
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 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.health.contributor.Health;

View File

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

View File

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

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