Skip to content

Commit

Permalink
Merge pull request quarkusio#4757 from geoand/quarkusio#4719
Browse files Browse the repository at this point in the history
Make Jackson + JSON-B @JsonSerializer/Deserializer for fields work in native
  • Loading branch information
gsmet authored Oct 23, 2019
2 parents caf49f6 + 63ba182 commit 0ff61b2
Show file tree
Hide file tree
Showing 13 changed files with 544 additions and 7 deletions.
1 change: 1 addition & 0 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ stages:
timeoutInMinutes: 25
modules:
- jackson
- jsonb
- jgit
- kogito
- kubernetes-client
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package io.quarkus.jackson.deployment;

import java.util.Collection;
import static org.jboss.jandex.AnnotationTarget.Kind.CLASS;
import static org.jboss.jandex.AnnotationTarget.Kind.FIELD;
import static org.jboss.jandex.AnnotationTarget.Kind.METHOD;

import java.util.HashSet;
import java.util.List;
import java.util.Set;
Expand All @@ -20,6 +23,7 @@
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.module.SimpleModule;

import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
Expand All @@ -41,6 +45,7 @@
public class JacksonProcessor {

private static final DotName JSON_DESERIALIZE = DotName.createSimple(JsonDeserialize.class.getName());
private static final DotName JSON_SERIALIZE = DotName.createSimple(JsonSerialize.class.getName());
private static final DotName BUILDER_VOID = DotName.createSimple(Void.class.getName());

@Inject
Expand Down Expand Up @@ -73,21 +78,40 @@ void register() {
ignoredDotNames.add(ignoreJsonDeserializeClassBuildItem.getDotName());
}

Collection<AnnotationInstance> pojoBuilderInstances = index.getAnnotations(JSON_DESERIALIZE);
for (AnnotationInstance pojoBuilderInstance : pojoBuilderInstances) {
if (AnnotationTarget.Kind.CLASS.equals(pojoBuilderInstance.target().kind())) {
DotName dotName = pojoBuilderInstance.target().asClass().name();
// handle the various @JsonDeserialize cases
for (AnnotationInstance deserializeInstance : index.getAnnotations(JSON_DESERIALIZE)) {
AnnotationTarget annotationTarget = deserializeInstance.target();
if (CLASS.equals(annotationTarget.kind())) {
DotName dotName = annotationTarget.asClass().name();
if (!ignoredDotNames.contains(dotName)) {
addReflectiveHierarchyClass(dotName);
}

AnnotationValue annotationValue = pojoBuilderInstance.value("builder");
AnnotationValue annotationValue = deserializeInstance.value("builder");
if (null != annotationValue && AnnotationValue.Kind.CLASS.equals(annotationValue.kind())) {
DotName builderClassName = annotationValue.asClass().name();
if (!BUILDER_VOID.equals(builderClassName)) {
addReflectiveHierarchyClass(builderClassName);
}
}
} else if (FIELD.equals(annotationTarget.kind()) || METHOD.equals(annotationTarget.kind())) {
AnnotationValue usingValue = deserializeInstance.value("using");
if (usingValue != null) {
// the Deserializers are constructed internally by Jackson using a no-args constructor
reflectiveClass.produce(new ReflectiveClassBuildItem(false, false, usingValue.asClass().toString()));
}
}
}

// handle the various @JsonSerialize cases
for (AnnotationInstance serializeInstance : index.getAnnotations(JSON_SERIALIZE)) {
AnnotationTarget annotationTarget = serializeInstance.target();
if (FIELD.equals(annotationTarget.kind()) || METHOD.equals(annotationTarget.kind())) {
AnnotationValue usingValue = serializeInstance.value("using");
if (usingValue != null) {
// the Deserializers are constructed internally by Jackson using a no-args constructor
reflectiveClass.produce(new ReflectiveClassBuildItem(false, false, usingValue.asClass().toString()));
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package io.quarkus.jsonb.deployment;

import static org.jboss.jandex.AnnotationTarget.Kind.FIELD;
import static org.jboss.jandex.AnnotationTarget.Kind.METHOD;

import java.util.HashSet;
import java.util.List;
import java.util.Set;
Expand All @@ -8,12 +11,18 @@
import javax.inject.Singleton;
import javax.json.bind.JsonbConfig;
import javax.json.bind.adapter.JsonbAdapter;
import javax.json.bind.annotation.JsonbTypeDeserializer;
import javax.json.bind.annotation.JsonbTypeSerializer;
import javax.json.bind.serializer.JsonbDeserializer;
import javax.json.bind.serializer.JsonbSerializer;

import org.eclipse.yasson.JsonBindingProvider;
import org.eclipse.yasson.spi.JsonbComponentInstanceCreator;
import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationTarget;
import org.jboss.jandex.AnnotationValue;
import org.jboss.jandex.DotName;
import org.jboss.jandex.IndexView;

import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem;
Expand All @@ -22,6 +31,7 @@
import io.quarkus.arc.processor.BeanInfo;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.deployment.builditem.substrate.ReflectiveClassBuildItem;
import io.quarkus.deployment.builditem.substrate.ServiceProviderBuildItem;
import io.quarkus.deployment.builditem.substrate.SubstrateResourceBundleBuildItem;
Expand All @@ -40,11 +50,15 @@ public class JsonbProcessor {

static final DotName JSONB_ADAPTER_NAME = DotName.createSimple(JsonbAdapter.class.getName());

private static final DotName JSONB_TYPE_SERIALIZER = DotName.createSimple(JsonbTypeSerializer.class.getName());
private static final DotName JSONB_TYPE_DESERIALIZER = DotName.createSimple(JsonbTypeDeserializer.class.getName());

@BuildStep
void build(BuildProducer<ReflectiveClassBuildItem> reflectiveClass,
BuildProducer<SubstrateResourceBundleBuildItem> resourceBundle,
BuildProducer<ServiceProviderBuildItem> serviceProvider,
BuildProducer<AdditionalBeanBuildItem> additionalBeans) {
BuildProducer<AdditionalBeanBuildItem> additionalBeans,
CombinedIndexBuildItem combinedIndexBuildItem) {
reflectiveClass.produce(new ReflectiveClassBuildItem(false, false,
JsonBindingProvider.class.getName()));

Expand All @@ -55,6 +69,29 @@ void build(BuildProducer<ReflectiveClassBuildItem> reflectiveClass,

// this needs to be registered manually since the runtime module is not indexed by Jandex
additionalBeans.produce(new AdditionalBeanBuildItem(JsonbProducer.class));

IndexView index = combinedIndexBuildItem.getIndex();

// handle the various @JsonSerialize cases
for (AnnotationInstance serializeInstance : index.getAnnotations(JSONB_TYPE_SERIALIZER)) {
registerInstance(reflectiveClass, serializeInstance);
}

// handle the various @JsonDeserialize cases
for (AnnotationInstance deserializeInstance : index.getAnnotations(JSONB_TYPE_DESERIALIZER)) {
registerInstance(reflectiveClass, deserializeInstance);
}
}

private void registerInstance(BuildProducer<ReflectiveClassBuildItem> reflectiveClass, AnnotationInstance instance) {
AnnotationTarget annotationTarget = instance.target();
if (FIELD.equals(annotationTarget.kind()) || METHOD.equals(annotationTarget.kind())) {
AnnotationValue value = instance.value();
if (value != null) {
// the Deserializers are constructed internally by JSON-B using a no-args constructor
reflectiveClass.produce(new ReflectiveClassBuildItem(false, false, value.asClass().toString()));
}
}
}

@BuildStep
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package io.quarkus.reproducer.jacksonbuilder.model;

import java.io.IOException;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
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.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;

import io.quarkus.runtime.annotations.RegisterForReflection;

@RegisterForReflection
@JsonIgnoreProperties(ignoreUnknown = true)
public class ModelWithSerializerAndDeserializerOnField {

private String name;

@JsonDeserialize(using = InnerDeserializer.class)
@JsonSerialize(using = InnerSerializer.class)
private Inner inner;

public ModelWithSerializerAndDeserializerOnField() {
}

public ModelWithSerializerAndDeserializerOnField(String name, Inner inner) {
this.name = name;
this.inner = inner;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Inner getInner() {
return inner;
}

public void setInner(Inner inner) {
this.inner = inner;
}

@JsonIgnoreProperties(ignoreUnknown = true)
public static class Inner {
private String someValue;

public Inner() {
}

public Inner(String someValue) {
this.someValue = someValue;
}

public String getSomeValue() {
return someValue;
}

public void setSomeValue(String someValue) {
this.someValue = someValue;
}
}

public static class InnerDeserializer extends JsonDeserializer<Inner> {

@Override
public Inner deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
return new Inner("immutable");
}
}

public static class InnerSerializer extends JsonSerializer<Inner> {
@Override
public void serialize(Inner value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeStartObject();
gen.writeStringField("someValue", "unchangeable");
gen.writeEndObject();
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package io.quarkus.reproducer;

import java.io.IOException;

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import com.fasterxml.jackson.databind.ObjectMapper;

import io.quarkus.reproducer.jacksonbuilder.model.ModelWithSerializerAndDeserializerOnField;

@Path("fieldserder")
public class ModelWithSerializerDeserializerOnFieldResource {

private final ObjectMapper objectMapper;

public ModelWithSerializerDeserializerOnFieldResource(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}

@POST
@Produces(MediaType.TEXT_PLAIN)
@Consumes(MediaType.APPLICATION_JSON)
public String post(String body) throws IOException {
ModelWithSerializerAndDeserializerOnField input = objectMapper.readValue(body,
ModelWithSerializerAndDeserializerOnField.class);
return input.getName() + "/" + input.getInner().getSomeValue();
}

@GET
@Path("/{name}/{someValue}")
@Produces(MediaType.APPLICATION_JSON)
public String get(@PathParam("name") String name, @PathParam("someValue") String someValue) throws IOException {
ModelWithSerializerAndDeserializerOnField input = new ModelWithSerializerAndDeserializerOnField(name,
new ModelWithSerializerAndDeserializerOnField.Inner(someValue));
return objectMapper.writeValueAsString(input);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.quarkus.reproducer;

import io.quarkus.test.junit.SubstrateTest;

@SubstrateTest
public class ModelWithSerializerAndDeserializerOnFieldResourceIT extends ModelWithSerializerAndDeserializerOnFieldResourceTest {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package io.quarkus.reproducer;

import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;

import java.io.IOException;

import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.databind.ObjectMapper;

import io.quarkus.reproducer.jacksonbuilder.model.ModelWithSerializerAndDeserializerOnField;
import io.quarkus.test.junit.QuarkusTest;

@QuarkusTest
public class ModelWithSerializerAndDeserializerOnFieldResourceTest {

@Test
public void testSerializer() throws IOException {
given()
.contentType("application/json")
.when().get("/fieldserder/tester/whatever")
.then()
.statusCode(200)
.body("name", equalTo("tester"))
.body("inner.someValue", equalTo("unchangeable"));
}

@Test
public void testDeserializer() throws IOException {
ObjectMapper objectMapper = new ObjectMapper();

given()
.contentType("application/json")
.body(objectMapper.writeValueAsString(
new ModelWithSerializerAndDeserializerOnField("tester",
new ModelWithSerializerAndDeserializerOnField.Inner())))
.when().post("/fieldserder")
.then()
.statusCode(200)
.body(is("tester/immutable"));
}
}
Loading

0 comments on commit 0ff61b2

Please sign in to comment.