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 0364ef8 commit acb52a9
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,6 @@ protected static String ucFirst(String name) {
return name.substring(0, 1).toUpperCase() + name.substring(1);
}

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

protected static boolean vetoedClassName(String className) {
return className.startsWith("java.") || className.startsWith("jakarta.") || className.startsWith("io.vertx.core.json.");
}
Expand Down Expand Up @@ -224,7 +220,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 Down
Original file line number Diff line number Diff line change
Expand Up @@ -270,31 +270,38 @@ 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 String writeMethodForPrimitiveFields(String typeName) {
return switch (typeName) {
case "java.lang.String", "char", "java.lang.Character" -> "writeString";
Expand All @@ -306,6 +313,13 @@ private String writeMethodForPrimitiveFields(String typeName) {
};
}

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 BytecodeCreator writeFieldBranch(ClassCreator classCreator, MethodCreator serialize, FieldSpecs fieldSpecs) {
String[] rolesAllowed = fieldSpecs.rolesAllowed();
if (rolesAllowed != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -491,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 @@ -674,4 +674,21 @@ public void testEcho() {
.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());
}
}

0 comments on commit acb52a9

Please sign in to comment.