Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make Jackson + JSON-B @JsonSerializer/Deserializer for fields work in native #4757

Merged
merged 2 commits into from
Oct 23, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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