Skip to content

Commit

Permalink
Make generated Jackson serializers to work with null values of boxed …
Browse files Browse the repository at this point in the history
…types
  • Loading branch information
mariofusco committed Sep 9, 2024
1 parent 9fb4cb8 commit f1a87b2
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -305,31 +305,45 @@ private void writeField(ClassInfo classInfo, FieldSpecs fieldSpecs, BytecodeCrea
ResultHandle serializerProvider, ResultHandle valueHandle) {
String pkgName = classInfo.name().packagePrefixName().toString();
generatedFields.computeIfAbsent(pkgName, pkg -> new HashSet<>()).add(fieldSpecs.jsonName);
MethodDescriptor writeFieldName = MethodDescriptor.ofMethod(JSON_GEN_CLASS_NAME, "writeFieldName", void.class,
SerializableString.class);
ResultHandle serStringHandle = bytecode.readStaticField(
FieldDescriptor.of(pkgName + "." + SER_STRINGS_CLASS_NAME, fieldSpecs.jsonName,
SerializedString.class.getName()));
bytecode.invokeVirtualMethod(writeFieldName, jsonGenerator, serStringHandle);

ResultHandle arg = fieldSpecs.toValueReaderHandle(bytecode, valueHandle);
String typeName = fieldSpecs.fieldType.name().toString();
String primitiveMethodName = writeMethodForPrimitiveFields(typeName);

if (primitiveMethodName != null) {
BytecodeCreator primitiveBytecode = isBoxedPrimitive(typeName) ? bytecode.ifNotNull(arg).trueBranch() : bytecode;
writeFieldName(fieldSpecs, primitiveBytecode, jsonGenerator, pkgName);
MethodDescriptor primitiveWriter = MethodDescriptor.ofMethod(JSON_GEN_CLASS_NAME, primitiveMethodName, "void",
fieldSpecs.writtenType());
bytecode.invokeVirtualMethod(primitiveWriter, jsonGenerator, arg);
primitiveBytecode.invokeVirtualMethod(primitiveWriter, jsonGenerator, arg);
return;
}

registerTypeToBeGenerated(fieldSpecs.fieldType, typeName);

writeFieldName(fieldSpecs, bytecode, jsonGenerator, pkgName);
MethodDescriptor writeMethod = MethodDescriptor.ofMethod(JSON_GEN_CLASS_NAME, "writePOJO",
void.class, Object.class);
bytecode.invokeVirtualMethod(writeMethod, jsonGenerator, arg);
}

private static void writeFieldName(FieldSpecs fieldSpecs, BytecodeCreator bytecode, ResultHandle jsonGenerator,
String pkgName) {
MethodDescriptor writeFieldName = MethodDescriptor.ofMethod(JSON_GEN_CLASS_NAME, "writeFieldName", void.class,
SerializableString.class);
ResultHandle serStringHandle = bytecode.readStaticField(
FieldDescriptor.of(pkgName + "." + SER_STRINGS_CLASS_NAME, fieldSpecs.jsonName,
SerializedString.class.getName()));
bytecode.invokeVirtualMethod(writeFieldName, jsonGenerator, serStringHandle);
}

private boolean isBoxedPrimitive(String typeName) {
return "java.lang.Character".equals(typeName) || "java.lang.Short".equals(typeName)
|| "java.lang.Integer".equals(typeName) || "java.lang.Long".equals(typeName)
|| "java.lang.Float".equals(typeName) || "java.lang.Double".equals(typeName)
|| "java.lang.Boolean".equals(typeName);
}

private void registerTypeToBeGenerated(Type fieldType, String typeName) {
if (!isCollectionType(fieldType, typeName)) {
registerTypeToBeGenerated(typeName);
Expand Down Expand Up @@ -489,7 +503,7 @@ private MethodInfo getterMethodInfo(ClassInfo classInfo, FieldInfo fieldInfo) {
if (namedAccessor != null) {
return namedAccessor;
}
String methodName = (isBooleanType(fieldInfo.type().name().toString()) ? "is" : "get") + ucFirst(fieldInfo.name());
String methodName = (fieldInfo.type().name().toString().equals("boolean") ? "is" : "get") + ucFirst(fieldInfo.name());
return findMethod(classInfo, methodName);
}

Expand All @@ -503,10 +517,6 @@ private static String ucFirst(String name) {
return name.substring(0, 1).toUpperCase() + name.substring(1);
}

private static boolean isBooleanType(String type) {
return type.equals("boolean") || type.equals("java.lang.Boolean");
}

private static boolean vetoedClassName(String className) {
return className.startsWith("java.") || className.startsWith("jakarta.") || className.startsWith("io.vertx.core.json.");
}
Expand Down Expand Up @@ -579,9 +589,13 @@ private String fieldName() {

private String fieldNameFromMethod(MethodInfo methodInfo) {
String methodName = methodInfo.name();
return isBooleanType(methodInfo.returnType().toString())
? methodName.substring(2, 3).toLowerCase() + methodName.substring(3)
: methodName.substring(3, 4).toLowerCase() + methodName.substring(4);
if (methodName.startsWith("is")) {
return methodName.substring(2, 3).toLowerCase() + methodName.substring(3);
}
if (methodName.startsWith("get")) {
return methodName.substring(3, 4).toLowerCase() + methodName.substring(4);
}
return methodName;
}

boolean hasUnknownAnnotation() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,8 @@ public ObjectWriter apply(ObjectMapper objectMapper, Type type) {
type = ((ParameterizedType) type).getActualTypeArguments()[0];
}
if (!type.getTypeName().equals(Person.class.getName())) {
throw new IllegalArgumentException("Only Person type can be handled");
throw new IllegalArgumentException(
"Type'" + type.getTypeName() + "' cannot be handled. Only 'Person' type is valid");
}
return objectMapper.writer().without(JsonWriteFeature.QUOTE_FIELD_NAMES);
}
Expand All @@ -490,7 +491,8 @@ public ObjectReader apply(ObjectMapper objectMapper, Type type) {
type = ((ParameterizedType) type).getActualTypeArguments()[0];
}
if (!type.getTypeName().equals(Person.class.getName())) {
throw new IllegalArgumentException("Only Person type can be handled");
throw new IllegalArgumentException(
"Type'" + type.getTypeName() + "' cannot be handled. Only 'Person' type is valid");
}
return objectMapper.reader().with(JsonReadFeature.ALLOW_UNQUOTED_FIELD_NAMES);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,24 @@ public void testEcho() {
.body("publicName", Matchers.is("Leo"))
.body("privateName", Matchers.nullValue())
.body("age", Matchers.is(5))
.body("vaccinated", Matchers.is(true))
.body("veterinarian.name", Matchers.is("Dolittle"))
.body("veterinarian.title", Matchers.nullValue());
}

@Test
public void testEchoWithMissingPrimitive() {
RestAssured
.with()
.body("{\"publicName\":\"Leo\",\"veterinarian\":{\"name\":\"Dolittle\"},\"age\":5}")
.contentType("application/json; charset=utf-8")
.post("/simple/dog-echo")
.then()
.statusCode(200)
.contentType("application/json")
.body("publicName", Matchers.is("Leo"))
.body("privateName", Matchers.nullValue())
.body("age", Matchers.is(5))
.body("veterinarian.name", Matchers.is("Dolittle"))
.body("veterinarian.title", Matchers.nullValue());
}
Expand Down

0 comments on commit f1a87b2

Please sign in to comment.