Skip to content

Commit 775bea6

Browse files
Restore @JsonbTypeAdapter support and fix Jackson migration issues
The Jackson migration PR dropped @JsonbTypeAdapter support entirely, but this annotation is part of the MicroProfile spec (JSON-B) and must keep working. This commit restores it by converting @JsonbTypeAdapter to the internal AdaptWith model at schema-build time, and ensures the runtime adapter invocation works with Jackson instead of relying on JSON-B to handle it automatically. Fixes: - Restore @JsonbTypeAdapter handling in AdaptWithHelper (schema builder) - Remove isJsonB() exclusion in InputFieldsInfo so JsonB adapters are invoked explicitly at runtime (Jackson does not handle them) - Add ComplexMapKeys module to JacksonCreator for Map fields with non-trivial key types that Jackson cannot deserialize by default - Fix micrometer property name typo (verison -> version)
1 parent 47bd545 commit 775bea6

9 files changed

Lines changed: 127 additions & 28 deletions

File tree

common/jackson-jsonb-compat/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<parent>
77
<groupId>io.smallrye</groupId>
88
<artifactId>smallrye-graphql-common-parent</artifactId>
9-
<version>3.0.0.Beta4-SNAPSHOT</version>
9+
<version>3.0.0.Beta5-SNAPSHOT</version>
1010
</parent>
1111

1212
<artifactId>smallrye-graphql-jackson-jsonb-compat</artifactId>

common/schema-builder/src/main/java/io/smallrye/graphql/schema/helper/AdaptWithHelper.java

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import org.jboss.jandex.ClassInfo;
99
import org.jboss.jandex.ParameterizedType;
1010
import org.jboss.jandex.Type;
11-
import org.jboss.logging.Logger;
1211

1312
import io.smallrye.graphql.schema.Annotations;
1413
import io.smallrye.graphql.schema.Classes;
@@ -28,8 +27,6 @@
2827
*/
2928
public class AdaptWithHelper {
3029

31-
private static final Logger LOG = Logger.getLogger(AdaptWithHelper.class.getName());
32-
3330
private AdaptWithHelper() {
3431
}
3532

@@ -137,11 +134,23 @@ private static AdapterType getAdapterType(Annotations annotations) {
137134
}
138135
}
139136

140-
// Warn about deprecated @JsonbTypeAdapter
141-
if (annotations.containsOneOfTheseAnnotations(Annotations.JAKARTA_JSONB_TYPE_ADAPTER,
142-
Annotations.JAVAX_JSONB_TYPE_ADAPTER)) {
143-
LOG.warn("@JsonbTypeAdapter is no longer supported. "
144-
+ "Use @AdaptWith or Jackson @JsonSerialize/@JsonDeserialize annotations instead.");
137+
if (annotations.containsOneOfTheseAnnotations(Annotations.JAKARTA_JSONB_TYPE_ADAPTER)) {
138+
AnnotationValue annotationValue = annotations.getAnnotationValue(Annotations.JAKARTA_JSONB_TYPE_ADAPTER);
139+
if (annotationValue != null) {
140+
AdaptWith adaptWith = new AdaptWith(Classes.JAKARTA_JSONB_ADAPTER.toString(),
141+
"adaptFromJson", "adaptToJson");
142+
Type type = annotationValue.asClass();
143+
return new AdapterType(type, adaptWith);
144+
}
145+
}
146+
if (annotations.containsOneOfTheseAnnotations(Annotations.JAVAX_JSONB_TYPE_ADAPTER)) {
147+
AnnotationValue annotationValue = annotations.getAnnotationValue(Annotations.JAVAX_JSONB_TYPE_ADAPTER);
148+
if (annotationValue != null) {
149+
AdaptWith adaptWith = new AdaptWith(Classes.JAVAX_JSONB_ADAPTER.toString(),
150+
"adaptFromJson", "adaptToJson");
151+
Type type = annotationValue.asClass();
152+
return new AdapterType(type, adaptWith);
153+
}
145154
}
146155

147156
// Jackson @JsonSerialize(converter=...)

common/schema-builder/src/test/java/io/smallrye/graphql/index/app/MappingResource.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import java.util.Currency;
44

5+
import jakarta.json.bind.annotation.JsonbTypeAdapter;
6+
57
import org.eclipse.microprofile.graphql.Description;
68
import org.eclipse.microprofile.graphql.GraphQLApi;
79
import org.eclipse.microprofile.graphql.Mutation;
@@ -35,6 +37,7 @@ public static class Data {
3537
public String name;
3638

3739
@ToScalar(Scalar.String.class)
40+
@JsonbTypeAdapter(CurrencyAdapter.class)
3841
public Currency currency;
3942

4043
@ToScalar(Scalar.String.class)

pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
<version.graphql-java-federation>6.0.0</version.graphql-java-federation>
3838
<version.graphql-java>25.0</version.graphql-java>
3939
<version.extended-scalars>24.0</version.extended-scalars>
40-
<verison.io.micrometer>1.16.5</verison.io.micrometer>
40+
<version.io.micrometer>1.16.5</version.io.micrometer>
4141
<version.vertx>5.0.12</version.vertx>
4242
<version.smallrye-opentelemetry>2.13.0</version.smallrye-opentelemetry>
4343

@@ -141,7 +141,7 @@
141141
<dependency>
142142
<groupId>io.micrometer</groupId>
143143
<artifactId>micrometer-core</artifactId>
144-
<version>${verison.io.micrometer}</version>
144+
<version>${version.io.micrometer}</version>
145145
</dependency>
146146

147147
<dependency>

server/implementation/src/main/java/io/smallrye/graphql/execution/datafetcher/helper/ArgumentHelper.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,8 @@ private Object adaptInputWith(Field field, Object argumentValue, DataFetchingEnv
213213
argumentValue = correctComplexObjectFromMap((Map) argumentValue, field, dfe);
214214
}
215215

216+
argumentValue = coerceToParameterType(argumentValue, reflectionInvoker.getMethod());
217+
216218
try {
217219
Object adaptedObject = reflectionInvoker.invoke(argumentValue);
218220
return adaptedObject;
@@ -224,6 +226,36 @@ private Object adaptInputWith(Field field, Object argumentValue, DataFetchingEnv
224226
return argumentValue;
225227
}
226228

229+
private static Object coerceToParameterType(Object value, java.lang.reflect.Method method) {
230+
if (value == null || method == null || method.getParameterCount() == 0) {
231+
return value;
232+
}
233+
Class<?> expected = method.getParameterTypes()[0];
234+
if (expected.isInstance(value)) {
235+
return value;
236+
}
237+
if (value instanceof Number) {
238+
Number num = (Number) value;
239+
if (expected == Long.class || expected == long.class) {
240+
return num.longValue();
241+
} else if (expected == Integer.class || expected == int.class) {
242+
return num.intValue();
243+
} else if (expected == Short.class || expected == short.class) {
244+
return num.shortValue();
245+
} else if (expected == Byte.class || expected == byte.class) {
246+
return num.byteValue();
247+
} else if (expected == Double.class || expected == double.class) {
248+
return num.doubleValue();
249+
} else if (expected == Float.class || expected == float.class) {
250+
return num.floatValue();
251+
}
252+
}
253+
if (expected == String.class) {
254+
return value.toString();
255+
}
256+
return value;
257+
}
258+
227259
private Object transformInput(Field field, Object object) throws AbstractDataFetcherException {
228260
if (object == null) {
229261
return null;

server/implementation/src/main/java/io/smallrye/graphql/json/InputFieldsInfo.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,8 @@ protected static void register(InputType inputType) {
4444
&& !field.getTransformation().isJsonB()) {
4545
fieldsThatNeedsTransformation.put(field.getName(), field);
4646
}
47-
// See if there is a adapter
48-
if (field.isAdaptingWith()
49-
&& !field.getAdaptWith().isJsonB()) {
47+
// See if there is an adapter
48+
if (field.isAdaptingWith()) {
5049
fieldsThatNeedsAdaptingWith.put(field.getName(), field);
5150
// See if there is a map (default adapter)
5251
} else if (field.hasWrapper() && field.getWrapper().isMap()) {

server/implementation/src/main/java/io/smallrye/graphql/json/JacksonCreator.java

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,20 @@
99
import com.fasterxml.jackson.annotation.JsonInclude;
1010
import com.fasterxml.jackson.core.JsonGenerator;
1111
import com.fasterxml.jackson.core.JsonParser;
12+
import com.fasterxml.jackson.databind.BeanDescription;
13+
import com.fasterxml.jackson.databind.DeserializationConfig;
1214
import com.fasterxml.jackson.databind.DeserializationContext;
1315
import com.fasterxml.jackson.databind.DeserializationFeature;
16+
import com.fasterxml.jackson.databind.JavaType;
1417
import com.fasterxml.jackson.databind.JsonDeserializer;
18+
import com.fasterxml.jackson.databind.JsonMappingException;
1519
import com.fasterxml.jackson.databind.JsonNode;
1620
import com.fasterxml.jackson.databind.JsonSerializer;
21+
import com.fasterxml.jackson.databind.KeyDeserializer;
1722
import com.fasterxml.jackson.databind.ObjectMapper;
1823
import com.fasterxml.jackson.databind.SerializationFeature;
1924
import com.fasterxml.jackson.databind.SerializerProvider;
25+
import com.fasterxml.jackson.databind.deser.KeyDeserializers;
2026
import com.fasterxml.jackson.databind.module.SimpleModule;
2127
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
2228
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
@@ -92,6 +98,7 @@ private static ObjectMapper createDefaultObjectMapper() {
9298
mapper.registerModule(new JavaTimeModule());
9399
mapper.registerModule(new Jdk8Module());
94100
mapper.registerModule(CUSTOM_SCALARS_MODULE);
101+
mapper.registerModule(createComplexKeyModule());
95102
mapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
96103
mapper.enable(SerializationFeature.INDENT_OUTPUT);
97104
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
@@ -223,6 +230,50 @@ public jakarta.json.JsonValue deserialize(JsonParser p, DeserializationContext c
223230
return module;
224231
}
225232

233+
private static com.fasterxml.jackson.databind.Module createComplexKeyModule() {
234+
return new com.fasterxml.jackson.databind.Module() {
235+
@Override
236+
public String getModuleName() {
237+
return "ComplexMapKeys";
238+
}
239+
240+
@Override
241+
public com.fasterxml.jackson.core.Version version() {
242+
return com.fasterxml.jackson.core.Version.unknownVersion();
243+
}
244+
245+
@Override
246+
public void setupModule(SetupContext context) {
247+
context.addKeyDeserializers(new KeyDeserializers() {
248+
@Override
249+
public KeyDeserializer findKeyDeserializer(JavaType keyType, DeserializationConfig config,
250+
BeanDescription beanDesc) throws JsonMappingException {
251+
Class<?> raw = keyType.getRawClass();
252+
if (!isComplexKeyType(raw)) {
253+
return null;
254+
}
255+
return new KeyDeserializer() {
256+
@Override
257+
public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException {
258+
ObjectMapper mapper = (ObjectMapper) ctxt.getParser().getCodec();
259+
return mapper.readValue(key, keyType);
260+
}
261+
};
262+
}
263+
});
264+
}
265+
};
266+
}
267+
268+
private static boolean isComplexKeyType(Class<?> type) {
269+
if (type.isPrimitive() || type.isEnum() || type.isArray()) {
270+
return false;
271+
}
272+
String name = type.getName();
273+
return !name.startsWith("java.") && !name.startsWith("javax.")
274+
&& !name.startsWith("jakarta.") && !name.startsWith("com.fasterxml.");
275+
}
276+
226277
private static jakarta.json.JsonValue jacksonNodeToJsonPValue(JsonNode node) {
227278
if (node == null || node.isNull()) {
228279
return jakarta.json.JsonValue.NULL;

server/implementation/src/test/java/io/smallrye/graphql/schema/SchemaTest.java

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import java.util.stream.Collectors;
2424

2525
import jakarta.annotation.security.RolesAllowed;
26+
import jakarta.json.bind.adapter.JsonbAdapter;
27+
import jakarta.json.bind.annotation.JsonbTypeAdapter;
2628

2729
import org.eclipse.microprofile.graphql.GraphQLApi;
2830
import org.eclipse.microprofile.graphql.NonNull;
@@ -540,7 +542,7 @@ public static class ModelA {
540542
@AdaptWith(CustomAdapter.class)
541543
public Collection<Long> someField;
542544

543-
@AdaptWith(CustomFloatToStringAdapter.class)
545+
@JsonbTypeAdapter(CustomJsonbTypeAdapter.class)
544546
public Float myOtherField;
545547

546548
public ModelA() {
@@ -605,23 +607,23 @@ public Long from(String a) throws Exception {
605607
}
606608
}
607609

608-
public static class CustomFloatToStringAdapter implements Adapter<Float, String> {
610+
public static class CustomJsonbTypeAdapter implements JsonbAdapter<Float, String> {
609611

610612
@Override
611-
public String to(Float o) throws Exception {
612-
return String.valueOf(o);
613+
public String adaptToJson(Float aFloat) throws Exception {
614+
return String.valueOf(aFloat);
613615
}
614616

615617
@Override
616-
public Float from(String a) throws Exception {
617-
return Float.valueOf(a);
618+
public Float adaptFromJson(String s) throws Exception {
619+
return Float.valueOf(s);
618620
}
619621
}
620622

621623
@Test
622624
public void adaptTest() {
623625
GraphQLSchema graphQLSchema = createGraphQLSchema(ModelA.class, ModelB.class, AdaptApi.class, CustomAdapter.class,
624-
CustomFloatToStringAdapter.class);
626+
CustomJsonbTypeAdapter.class);
625627

626628
GraphQLFieldDefinition modelAQuery = graphQLSchema.getQueryType().getField("someString");
627629
assertNotNull(modelAQuery);

server/integration-tests/src/test/java/io/smallrye/graphql/tests/adapting/AdaptTest.java

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
import java.net.URL;
66
import java.util.Collection;
77

8+
import jakarta.json.bind.adapter.JsonbAdapter;
9+
import jakarta.json.bind.annotation.JsonbTypeAdapter;
10+
811
import org.eclipse.microprofile.graphql.GraphQLApi;
912
import org.eclipse.microprofile.graphql.Query;
1013
import org.jboss.arquillian.container.test.api.Deployment;
@@ -29,7 +32,7 @@ public class AdaptTest {
2932
@Deployment
3033
public static WebArchive deployment() {
3134
return ShrinkWrap.create(WebArchive.class, "adapt-test.war")
32-
.addClasses(ModelA.class, ModelB.class, AdaptApi.class, MyOtherFieldAdapter.class, CustomAdapter.class);
35+
.addClasses(ModelA.class, ModelB.class, AdaptApi.class, CustomJsonbTypeAdapter.class, CustomAdapter.class);
3336
}
3437

3538
@ArquillianResource
@@ -42,7 +45,7 @@ public static class ModelA {
4245
@AdaptWith(CustomAdapter.class)
4346
public Collection<Long> someField;
4447

45-
@AdaptWith(MyOtherFieldAdapter.class)
48+
@JsonbTypeAdapter(CustomJsonbTypeAdapter.class)
4649
public String myOtherField;
4750

4851
public ModelA() {
@@ -107,16 +110,16 @@ public Long from(String a) throws Exception {
107110
}
108111
}
109112

110-
public static class MyOtherFieldAdapter implements Adapter<String, Integer> {
113+
public static class CustomJsonbTypeAdapter implements JsonbAdapter<String, Long> {
111114

112115
@Override
113-
public Integer to(String o) throws Exception {
114-
return Integer.parseInt(o);
116+
public Long adaptToJson(String s) throws Exception {
117+
return Long.parseLong(s);
115118
}
116119

117120
@Override
118-
public String from(Integer a) throws Exception {
119-
return String.valueOf(a);
121+
public String adaptFromJson(Long aLong) throws Exception {
122+
return String.valueOf(aLong);
120123
}
121124
}
122125

0 commit comments

Comments
 (0)