Skip to content

Commit 7623902

Browse files
YuriiBatkovychYurii Titov
andauthored
fix: duplicated discriminator serialisation issue (#1220)
* fix: duplicated discriminator serialisation issue * fix: add test cases * chore: small changes in assert message * chore: add release note --------- Co-authored-by: Yurii Titov <yuriit@backbase.com>
1 parent 76e02d1 commit 7623902

3 files changed

Lines changed: 42 additions & 13 deletions

File tree

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ It currently consists of
1515

1616
# Release Notes
1717
BOAT is still under development and subject to change.
18+
19+
## 0.17.75
20+
* Fixed duplicate serialization of the discriminator property in Jackson-based Java models by removing allowGetters = true from the @JsonIgnoreProperties annotation.
21+
1822
## 0.17.74
1923
* Swift5: Removed deprecated initializer from model objects. The initializer is now internal and only accessible through the Builder pattern, preventing breaking code from deprecation warnings.
2024

boat-scaffold/src/main/templates/boat-spring/typeInfoAnnotation.mustache

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
{{#-first}}
44
@JsonIgnoreProperties(
55
value = "{{{discriminator.propertyBaseName}}}", // ignore manually set {{{discriminator.propertyBaseName}}}, it will be automatically generated by Jackson during serialization
6-
allowGetters = true, // allows the {{{discriminator.propertyBaseName}}} to be set during serialization
76
allowSetters = true // allows the {{{discriminator.propertyBaseName}}} to be set during deserialization
87
)
98
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "{{{discriminator.propertyBaseName}}}", visible = true)

boat-scaffold/src/test/java/com/backbase/oss/codegen/java/BoatSpringTemplatesTests.java

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@
1212
import static org.hamcrest.Matchers.not;
1313
import static org.hamcrest.Matchers.nullValue;
1414
import static org.junit.jupiter.api.Assertions.assertEquals;
15+
import static org.junit.jupiter.api.Assertions.assertFalse;
1516
import static org.junit.jupiter.api.Assertions.assertNotNull;
1617
import static org.junit.jupiter.api.DynamicContainer.dynamicContainer;
1718
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
1819

1920
import com.backbase.oss.codegen.java.VerificationRunner.Verification;
21+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
2022
import com.fasterxml.jackson.databind.ObjectMapper;
2123
import com.fasterxml.jackson.databind.type.ArrayType;
2224
import com.fasterxml.jackson.databind.type.TypeBindings;
@@ -260,32 +262,42 @@ private void verifyGeneratedClasses(File projectDir) throws Exception {
260262

261263
private void verifyReceivableRequestModelJsonConversion(ClassLoader classLoader) throws InterruptedException {
262264
String testedModelClassName = buildReceivableRequestModelClassName();
265+
String parentModelClassName = buildPaymentRequestModelClassName();
263266
var objectMapper = new ObjectMapper();
264267
Runnable verificationRunnable = () -> {
265268
try {
266269
Class<?> modelClass = classLoader.loadClass(testedModelClassName);
270+
Class<?> parentClass = classLoader.loadClass(parentModelClassName);
267271
Constructor<?> constructor = modelClass.getConstructor(String.class, String.class, String.class);
268272
Object modelObject1 = constructor.newInstance("OK_status", "ref123", "EUR");
269273
Object modelObject2 = constructor.newInstance("BAD_status", "ref456", "USD");
270274

275+
// Serialize using the parent (discriminated) type so that Jackson's
276+
// is aware of full polymorphic context
277+
TypeFactory tf = TypeFactory.defaultInstance();
278+
271279
// serialize and deserialize list
272280
List<?> modelObjects = List.of(modelObject1, modelObject2);
273-
String serializedObjects = objectMapper.writeValueAsString(modelObjects);
281+
String serializedObjects = objectMapper.writerFor(
282+
tf.constructCollectionType(List.class, parentClass)
283+
).writeValueAsString(modelObjects);
274284
Object[] deserializedModelObjects = objectMapper.readValue(
275285
serializedObjects,
276286
ArrayType.construct(
277-
TypeFactory.defaultInstance().constructFromCanonical(modelClass.getName()),
287+
tf.constructFromCanonical(parentClass.getName()),
278288
TypeBindings.emptyBindings()
279289
)
280290
);
281291
assertEquals(modelObjects.size(), deserializedModelObjects.length);
282292
assertEquals(modelObject1.getClass(), deserializedModelObjects[0].getClass());
283293

284294
// serialize and deserialize single object
285-
String serializedObject1 = objectMapper.writeValueAsString(modelObject1);
286-
Object deserializedObject1 = objectMapper.readValue(serializedObject1, modelClass);
295+
String serializedObject1 = objectMapper.writerFor(parentClass).writeValueAsString(modelObject1);
296+
Object deserializedObject1 = objectMapper.readValue(serializedObject1, parentClass);
287297
assertEquals(modelClass, deserializedObject1.getClass());
288298

299+
verifyJsonIgnoreAnnotation(modelClass);
300+
verifyJsonIgnoreAnnotation(parentClass);
289301
} catch (Exception e) {
290302
throw new UnhandledException(e);
291303
}
@@ -296,6 +308,15 @@ private void verifyReceivableRequestModelJsonConversion(ClassLoader classLoader)
296308
);
297309
}
298310

311+
private void verifyJsonIgnoreAnnotation(Class<?> modelClass) {
312+
JsonIgnoreProperties ignoreAnnotation = modelClass.getAnnotation(JsonIgnoreProperties.class);
313+
314+
if (ignoreAnnotation != null) {
315+
assertFalse(ignoreAnnotation.allowGetters(),
316+
"Class " + modelClass.getName() + " should not have allowGetters=true in @JsonIgnoreProperties");
317+
}
318+
}
319+
299320
private void verifyMultiLineRequest(ClassLoader classLoader) throws InterruptedException {
300321
String testedModelClassName = buildMultiLineRequestModelClassName();
301322
Runnable verificationRunnable = () -> {
@@ -319,23 +340,28 @@ private void verifyMultiLineRequest(ClassLoader classLoader) throws InterruptedE
319340
* Build proper class name for `ReceivableRequest`.
320341
*/
321342
private String buildReceivableRequestModelClassName() {
343+
return buildModelClassName("ReceivableRequest");
344+
}
345+
346+
/**
347+
* Build proper class name for `PaymentRequest` (parent/discriminator base).
348+
*/
349+
private String buildPaymentRequestModelClassName() {
350+
return buildModelClassName("PaymentRequest");
351+
}
352+
353+
private String buildModelClassName(String baseName) {
322354
var modelPackage = param.name.replace('-', '.') + ".model";
323355
var classNameSuffix = org.apache.commons.lang3.StringUtils.capitalize(
324356
param.name.indexOf('-') > -1
325357
? CaseFormat.LOWER_HYPHEN.to(CaseFormat.LOWER_CAMEL, param.name)
326358
: param.name
327359
);
328-
return modelPackage + ".ReceivableRequest" + classNameSuffix;
360+
return modelPackage + "." + baseName + classNameSuffix;
329361
}
330362

331363
private String buildMultiLineRequestModelClassName() {
332-
var modelPackage = param.name.replace('-', '.') + ".model";
333-
var classNameSuffix = org.apache.commons.lang3.StringUtils.capitalize(
334-
param.name.indexOf('-') > -1
335-
? CaseFormat.LOWER_HYPHEN.to(CaseFormat.LOWER_CAMEL, param.name)
336-
: param.name
337-
);
338-
return modelPackage + ".MultiLinePaymentRequest" + classNameSuffix;
364+
return buildModelClassName("MultiLinePaymentRequest");
339365
}
340366

341367
private boolean findPattern(String filePattern, String linePattern) {

0 commit comments

Comments
 (0)