Skip to content

Commit a60e273

Browse files
committed
Fix reflection-free json serialization with @jsonvalue
1 parent 428006a commit a60e273

File tree

6 files changed

+102
-10
lines changed

6 files changed

+102
-10
lines changed

extensions/resteasy-reactive/rest-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/JacksonSerializerFactory.java

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,16 @@
1010
import java.util.HashMap;
1111
import java.util.HashSet;
1212
import java.util.Map;
13+
import java.util.Optional;
1314
import java.util.Set;
1415

1516
import org.jboss.jandex.ClassInfo;
1617
import org.jboss.jandex.FieldInfo;
1718
import org.jboss.jandex.IndexView;
1819
import org.jboss.jandex.MethodInfo;
20+
import org.jboss.jandex.VoidType;
1921

22+
import com.fasterxml.jackson.annotation.JsonValue;
2023
import com.fasterxml.jackson.core.JsonGenerator;
2124
import com.fasterxml.jackson.core.SerializableString;
2225
import com.fasterxml.jackson.core.io.SerializedString;
@@ -183,15 +186,27 @@ protected boolean createSerializationMethod(ClassInfo classInfo, ClassCreator cl
183186
"com.fasterxml.jackson.databind.SerializerProvider")
184187
.setModifiers(ACC_PUBLIC)
185188
.addException(IOException.class);
189+
186190
boolean valid = serializeObject(classInfo, classCreator, beanClassName, serialize);
187191
serialize.returnVoid();
188192
return valid;
189193
}
190194

191195
private boolean serializeObject(ClassInfo classInfo, ClassCreator classCreator, String beanClassName,
192196
MethodCreator serialize) {
197+
198+
var jsonValueFieldSpecs = jsonValueFieldSpecs(classInfo);
199+
if (jsonValueFieldSpecs == null) {
200+
return false;
201+
}
202+
193203
SerializationContext ctx = new SerializationContext(serialize, beanClassName);
194204

205+
if (jsonValueFieldSpecs.isPresent()) {
206+
serializeJsonValue(ctx, serialize, jsonValueFieldSpecs.get());
207+
return true;
208+
}
209+
195210
// jsonGenerator.writeStartObject();
196211
MethodDescriptor writeStartObject = MethodDescriptor.ofMethod(JSON_GEN_CLASS_NAME, "writeStartObject", "void");
197212
serialize.invokeVirtualMethod(writeStartObject, ctx.jsonGenerator);
@@ -212,6 +227,26 @@ private boolean serializeObject(ClassInfo classInfo, ClassCreator classCreator,
212227
return valid;
213228
}
214229

230+
private Optional<FieldSpecs> jsonValueFieldSpecs(ClassInfo classInfo) {
231+
var jsonValueMethodFieldSpecs = classInfo.methods().stream()
232+
.filter(mi -> mi.annotation(JsonValue.class) != null)
233+
.filter(this::isJsonValueMethod).findFirst().map(FieldSpecs::new);
234+
var jsonValueFieldFieldSpecs = classInfo.fields().stream()
235+
.filter(f -> f.annotation(JsonValue.class) != null)
236+
.findFirst().map(FieldSpecs::new);
237+
238+
if (jsonValueFieldFieldSpecs.isPresent()) {
239+
return jsonValueMethodFieldSpecs.isPresent() ? null : jsonValueFieldFieldSpecs;
240+
}
241+
return jsonValueMethodFieldSpecs;
242+
}
243+
244+
private void serializeJsonValue(SerializationContext ctx, MethodCreator bytecode, FieldSpecs jsonValueFieldSpecs) {
245+
String typeName = jsonValueFieldSpecs.fieldType.toString();
246+
ResultHandle arg = jsonValueFieldSpecs.toValueReaderHandle(bytecode, ctx.valueHandle);
247+
writeFieldValue(jsonValueFieldSpecs, bytecode, ctx, typeName, arg, null);
248+
}
249+
215250
private boolean serializeObjectData(ClassInfo classInfo, ClassCreator classCreator, MethodCreator serialize,
216251
SerializationContext ctx, Set<String> serializedFields) {
217252
return serializeFields(classInfo, classCreator, serialize, ctx, serializedFields) &&
@@ -258,6 +293,12 @@ private FieldSpecs fieldSpecsFromMethod(MethodInfo methodInfo) {
258293
return !Modifier.isStatic(methodInfo.flags()) && isGetterMethod(methodInfo) ? new FieldSpecs(methodInfo) : null;
259294
}
260295

296+
private boolean isJsonValueMethod(MethodInfo methodInfo) {
297+
return Modifier.isPublic(methodInfo.flags()) && !Modifier.isStatic(methodInfo.flags())
298+
&& methodInfo.parametersCount() == 0
299+
&& !methodInfo.returnType().equals(VoidType.VOID);
300+
}
301+
261302
private boolean isGetterMethod(MethodInfo methodInfo) {
262303
String methodName = methodInfo.name();
263304
return Modifier.isPublic(methodInfo.flags()) && !Modifier.isStatic(methodInfo.flags())
@@ -273,25 +314,36 @@ private void writeField(ClassInfo classInfo, FieldSpecs fieldSpecs, BytecodeCrea
273314
bytecode = checkInclude(bytecode, ctx, arg);
274315

275316
String typeName = fieldSpecs.fieldType.name().toString();
317+
writeFieldValue(fieldSpecs, bytecode, ctx, typeName, arg, pkgName);
318+
}
319+
320+
private void writeFieldValue(FieldSpecs fieldSpecs, BytecodeCreator bytecode, SerializationContext ctx, String typeName,
321+
ResultHandle arg, String pkgName) {
276322
String primitiveMethodName = writeMethodForPrimitiveFields(typeName);
277323

278324
if (primitiveMethodName != null) {
279325
BytecodeCreator primitiveBytecode = JacksonSerializationUtils.isBoxedPrimitive(typeName)
280326
? bytecode.ifNotNull(arg).trueBranch()
281327
: bytecode;
282-
writeFieldName(fieldSpecs, primitiveBytecode, ctx.jsonGenerator, pkgName);
328+
329+
if (pkgName != null) {
330+
writeFieldName(fieldSpecs, primitiveBytecode, ctx.jsonGenerator, pkgName);
331+
}
332+
283333
MethodDescriptor primitiveWriter = MethodDescriptor.ofMethod(JSON_GEN_CLASS_NAME, primitiveMethodName, "void",
284334
fieldSpecs.writtenType());
285335
primitiveBytecode.invokeVirtualMethod(primitiveWriter, ctx.jsonGenerator, arg);
286-
return;
287-
}
288336

289-
registerTypeToBeGenerated(fieldSpecs.fieldType, typeName);
337+
} else {
338+
if (pkgName != null) {
339+
registerTypeToBeGenerated(fieldSpecs.fieldType, typeName);
340+
writeFieldName(fieldSpecs, bytecode, ctx.jsonGenerator, pkgName);
341+
}
290342

291-
writeFieldName(fieldSpecs, bytecode, ctx.jsonGenerator, pkgName);
292-
MethodDescriptor writeMethod = MethodDescriptor.ofMethod(JSON_GEN_CLASS_NAME, "writePOJO",
293-
void.class, Object.class);
294-
bytecode.invokeVirtualMethod(writeMethod, ctx.jsonGenerator, arg);
343+
MethodDescriptor writeMethod = MethodDescriptor.ofMethod(JSON_GEN_CLASS_NAME, "writePOJO",
344+
void.class, Object.class);
345+
bytecode.invokeVirtualMethod(writeMethod, ctx.jsonGenerator, arg);
346+
}
295347
}
296348

297349
private static BytecodeCreator checkInclude(BytecodeCreator bytecode, SerializationContext ctx, ResultHandle arg) {

extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/AbstractSimpleJsonTest.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -796,6 +796,16 @@ public void testItemExtended() {
796796
.body("emailExtended", Matchers.is(emptyOrNullString()));
797797
}
798798

799+
@Test
800+
void testJsonValue() {
801+
RestAssured.given()
802+
.queryParam("value", 240)
803+
.post("/simple/json-value")
804+
.then()
805+
.statusCode(200)
806+
.body(Matchers.equalTo('\"' + new ItemId(240).format() + '\"'));
807+
}
808+
799809
@Test
800810
public void testPojoWithJsonCreator() {
801811
RestAssured
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package io.quarkus.resteasy.reactive.jackson.deployment.test;
2+
3+
import com.fasterxml.jackson.annotation.JsonCreator;
4+
import com.fasterxml.jackson.annotation.JsonValue;
5+
6+
public class ItemId {
7+
final int value;
8+
9+
@JsonCreator
10+
public ItemId(int value) {
11+
this.value = value;
12+
}
13+
14+
public int getValue() {
15+
return this.value;
16+
}
17+
18+
@JsonValue
19+
public String format() {
20+
return "ItemId:" + value;
21+
}
22+
}

extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonResource.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import jakarta.ws.rs.core.MediaType;
2121
import jakarta.ws.rs.core.Response;
2222

23+
import org.jboss.resteasy.reactive.RestQuery;
2324
import org.jboss.resteasy.reactive.RestResponse;
2425
import org.jboss.resteasy.reactive.server.ServerExceptionMapper;
2526

@@ -508,6 +509,13 @@ public ItemExtended getItemExtended() {
508509
return item;
509510
}
510511

512+
@POST
513+
@Path("/json-value")
514+
@Produces(MediaType.APPLICATION_JSON)
515+
public ItemId generate(@RestQuery int value) {
516+
return new ItemId(value);
517+
}
518+
511519
@POST
512520
@Path("/primitive-types-bean")
513521
public PrimitiveTypesBean echoPrimitiveTypesBean(PrimitiveTypesBean bean) {

extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public JavaArchive get() {
2727
Pond.class, FrogBodyParts.class, FrogBodyParts.BodyPart.class, ContainerDTO.class,
2828
NestedInterface.class, StateRecord.class, MapWrapper.class, GenericWrapper.class,
2929
Fruit.class, Price.class, DogRecord.class, ItemExtended.class, Book.class, LombokBook.class,
30-
PrimitiveTypesBean.class, PrimitiveTypesRecord.class, TokenResponse.class)
30+
PrimitiveTypesBean.class, PrimitiveTypesRecord.class, TokenResponse.class, ItemId.class)
3131
.addAsResource(new StringAsset("admin-expression=admin\n" +
3232
"user-expression=user\n" +
3333
"birth-date-roles=alice,bob\n"), "application.properties");

extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonWithReflectionFreeSerializersTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public JavaArchive get() {
2929
Pond.class, FrogBodyParts.class, FrogBodyParts.BodyPart.class, ContainerDTO.class,
3030
NestedInterface.class, StateRecord.class, MapWrapper.class, GenericWrapper.class,
3131
Fruit.class, Price.class, DogRecord.class, ItemExtended.class, Book.class, LombokBook.class,
32-
PrimitiveTypesBean.class, PrimitiveTypesRecord.class, TokenResponse.class)
32+
PrimitiveTypesBean.class, PrimitiveTypesRecord.class, TokenResponse.class, ItemId.class)
3333
.addAsResource(new StringAsset("admin-expression=admin\n" +
3434
"user-expression=user\n" +
3535
"birth-date-roles=alice,bob\n" +

0 commit comments

Comments
 (0)