Skip to content

Commit

Permalink
Disable Jackson's FAIL_ON_UNKNOWN_PROPERTIES by default
Browse files Browse the repository at this point in the history
Also provide a way to go back to the previous behavior.
  • Loading branch information
gsmet committed Nov 23, 2020
1 parent 08fe4a5 commit 542cb50
Show file tree
Hide file tree
Showing 10 changed files with 155 additions and 2 deletions.
9 changes: 8 additions & 1 deletion docs/src/main/asciidoc/rest-json.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,14 @@ It will allow to narrow down the number of JAX-RS providers (which can be seen a

==== Jackson

Quarkus makes it very easy to configure various Jackson settings via CDI beans. The simplest (and suggested) approach is to define a CDI bean of type `io.quarkus.jackson.ObjectMapperCustomizer`
In Quarkus, the default Jackson `ObjectMapper` obtained via CDI (and consumed by the Quarkus extensions) is configured to ignore unknown properties
(by disabling the `DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES` feature).

You can restore the default behavior of Jackson by setting `quarkus.jackson.fail-on-unknown-properties=true` in your `application.properties`
or on a per class basis via `@JsonIgnoreProperties(ignoreUnknown = false)`.

Also, Quarkus makes it very easy to configure various Jackson settings via CDI beans.
The simplest (and suggested) approach is to define a CDI bean of type `io.quarkus.jackson.ObjectMapperCustomizer`
inside of which any Jackson configuration can be applied.

An example where a custom module needs to be registered would look like so:
Expand Down
5 changes: 5 additions & 0 deletions extensions/jackson/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-internal</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,12 @@
import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.arc.deployment.GeneratedBeanBuildItem;
import io.quarkus.arc.deployment.GeneratedBeanGizmoAdaptor;
import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
import io.quarkus.deployment.Capability;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.CapabilityBuildItem;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
Expand All @@ -43,6 +46,9 @@
import io.quarkus.gizmo.MethodDescriptor;
import io.quarkus.gizmo.ResultHandle;
import io.quarkus.jackson.ObjectMapperCustomizer;
import io.quarkus.jackson.runtime.JacksonBuildTimeConfig;
import io.quarkus.jackson.runtime.JacksonConfigSupport;
import io.quarkus.jackson.runtime.JacksonRecorder;
import io.quarkus.jackson.runtime.ObjectMapperProducer;
import io.quarkus.jackson.spi.ClassPathJacksonModuleBuildItem;
import io.quarkus.jackson.spi.JacksonModuleBuildItem;
Expand Down Expand Up @@ -168,6 +174,16 @@ private void registerModuleIfOnClassPath(String moduleClassName,
}
}

@BuildStep
@Record(ExecutionTime.STATIC_INIT)
SyntheticBeanBuildItem pushConfigurationBean(JacksonRecorder jacksonRecorder,
JacksonBuildTimeConfig jacksonBuildTimeConfig) {
return SyntheticBeanBuildItem.configure(JacksonConfigSupport.class)
.scope(Singleton.class)
.supplier(jacksonRecorder.jacksonConfigSupport(jacksonBuildTimeConfig))
.done();
}

// Generate a ObjectMapperCustomizer bean that registers each serializer / deserializer as well as detected modules with the ObjectMapper
@BuildStep
void generateCustomizer(BuildProducer<GeneratedBeanBuildItem> generatedBeans,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package io.quarkus.jackson.deployment;

import javax.inject.Inject;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException;

import io.quarkus.test.QuarkusUnitTest;

public class JacksonFailOnUnknownPropertiesTest {

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withConfigurationResource("application-fail-on-unknown-properties.properties");

@Inject
ObjectMapper objectMapper;

@Test
public void testFailOnUnknownProperties() throws JsonMappingException, JsonProcessingException {
Assertions.assertThrows(UnrecognizedPropertyException.class,
() -> objectMapper.readValue("{\"property\": \"name\", \"unknownProperty\": \"unknown\"}", Pojo.class));
}

public static class Pojo {

public String property;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package io.quarkus.jackson.deployment;

import static org.junit.jupiter.api.Assertions.assertEquals;

import javax.inject.Inject;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import io.quarkus.test.QuarkusUnitTest;

public class JacksonIgnoreUnknownPropertiesTest {

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest();

@Inject
ObjectMapper objectMapper;

@Test
public void testIgnoreUnknownProperties() throws JsonMappingException, JsonProcessingException {
Pojo pojo = objectMapper.readValue("{\"property\": \"name\", \"unknownProperty\": \"unknown\"}", Pojo.class);
assertEquals("name", pojo.property);
}

public static class Pojo {

public String property;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
quarkus.jackson.fail-on-unknown-properties=true
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.quarkus.jackson.runtime;

import io.quarkus.runtime.annotations.ConfigItem;
import io.quarkus.runtime.annotations.ConfigRoot;

@ConfigRoot
public class JacksonBuildTimeConfig {

/**
* If enabled, Jackson will fail when encountering unknown properties.
* <p>
* You can still override it locally with {@code @JsonIgnoreProperties(ignoreUnknown = false)}.
*/
@ConfigItem(defaultValue = "false")
public boolean failOnUnknownProperties;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.quarkus.jackson.runtime;

public class JacksonConfigSupport {

private boolean failOnUnknownProperties;

public JacksonConfigSupport(boolean failOnUnknownProperties) {
this.failOnUnknownProperties = failOnUnknownProperties;
}

public boolean isFailOnUnknownProperties() {
return failOnUnknownProperties;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.quarkus.jackson.runtime;

import java.util.function.Supplier;

import io.quarkus.runtime.annotations.Recorder;

@Recorder
public class JacksonRecorder {

public Supplier<JacksonConfigSupport> jacksonConfigSupport(JacksonBuildTimeConfig jacksonBuildTimeConfig) {
return new Supplier<JacksonConfigSupport>() {

@Override
public JacksonConfigSupport get() {
return new JacksonConfigSupport(jacksonBuildTimeConfig.failOnUnknownProperties);
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import javax.enterprise.inject.Produces;
import javax.inject.Singleton;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

import io.quarkus.arc.DefaultBean;
Expand All @@ -20,8 +21,13 @@ public class ObjectMapperProducer {
@DefaultBean
@Singleton
@Produces
public ObjectMapper objectMapper(Instance<ObjectMapperCustomizer> customizers) {
public ObjectMapper objectMapper(Instance<ObjectMapperCustomizer> customizers,
JacksonConfigSupport jacksonConfigSupport) {
ObjectMapper objectMapper = new ObjectMapper();
if (!jacksonConfigSupport.isFailOnUnknownProperties()) {
// this feature is enabled by default, so we disable it
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
}
List<ObjectMapperCustomizer> sortedCustomizers = sortCustomizersInDescendingPriorityOrder(customizers);
for (ObjectMapperCustomizer customizer : sortedCustomizers) {
customizer.customize(objectMapper);
Expand Down

0 comments on commit 542cb50

Please sign in to comment.