Skip to content

Commit 21373ba

Browse files
authored
Fix #665: @JacksonXmlProperty with Records (#818)
1 parent 186988a commit 21373ba

File tree

4 files changed

+65
-3
lines changed

4 files changed

+65
-3
lines changed

release-notes/CREDITS

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ Severin Kistler (@kistlers)
7070
`java.util.Map`) fails
7171
(3.2.0)
7272

73+
Alex Olson (@alexkolson)
74+
* Reported #665: `@JacksonXmlProperty` appears to behave differently than
75+
`@JsonProperty` when used on java records
76+
(3.2.0)
77+
7378
Simon Cockx (@SimonCockx)
7479
* Reported #762: Unwrapping lists does not work inside `@JsonUnwrapped`
7580
(3.2.0)

release-notes/VERSION

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,15 @@ Version: 3.x (for earlier see VERSION-2.x)
4848
`java.util.Map`) fails
4949
(reported by Severin K)
5050
(fix by Christopher M)
51+
#665: `@JacksonXmlProperty` appears to behave differently than
52+
`@JsonProperty` when used on java records
53+
(reported by Alex O)
54+
(fix by @cowtowncoder, w/ Claude code)
5155
#735: Java Record with `@JacksonXmlText` stopped working with 2.18
5256
(reported by @akastyka))
5357
(fix by Christopher M)
5458
#762: Unwrapping lists does not work inside `@JsonUnwrapped`
55-
(reported by Simon C)
59+
(reported by Simon C)
5660
(fix by @cowtowncoder, w/ Claude code)
5761
#767: Unwrapped lists cannot be deserialized when using
5862
`JsonTypeInfo.As.EXISTING_PROPERTY`

src/main/java/tools/jackson/dataformat/xml/JacksonXmlAnnotationIntrospector.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public class JacksonXmlAnnotationIntrospector
2626
@SuppressWarnings("unchecked")
2727
private final static Class<? extends Annotation>[] ANNOTATIONS_TO_INFER_XML_PROP =
2828
(Class<? extends Annotation>[]) new Class<?>[] {
29-
JacksonXmlText.class, JacksonXmlElementWrapper.class
29+
JacksonXmlProperty.class, JacksonXmlText.class, JacksonXmlElementWrapper.class
3030
};
3131

3232
/**
@@ -243,7 +243,13 @@ protected PropertyName _findXmlName(Annotated a)
243243
{
244244
JacksonXmlProperty pann = _findAnnotation(a, JacksonXmlProperty.class);
245245
if (pann != null) {
246-
return PropertyName.construct(pann.localName(), pann.namespace());
246+
// [dataformat-xml#665]: empty localName should not produce an
247+
// empty-string PropertyName (causes "Duplicate creator property"
248+
// on records); return null so that the implicit name is used.
249+
String localName = pann.localName();
250+
if (localName != null && !localName.isEmpty()) {
251+
return PropertyName.construct(localName, pann.namespace());
252+
}
247253
}
248254
return null;
249255
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package tools.jackson.dataformat.xml.deser.records;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import tools.jackson.dataformat.xml.*;
6+
import tools.jackson.dataformat.xml.annotation.JacksonXmlProperty;
7+
8+
import static org.junit.jupiter.api.Assertions.*;
9+
10+
// [dataformat-xml#665]: @JacksonXmlProperty without localName on records
11+
// should not cause "Duplicate creator property" error
12+
public class XmlRecordDeser665Test extends XmlTestUtil
13+
{
14+
// Bare @JacksonXmlProperty (no arguments) on a record — was failing
15+
record SimpleRecord(@JacksonXmlProperty String name, String value) {}
16+
17+
// @JacksonXmlProperty(isAttribute=true) without localName — the main use case
18+
record AttributeRecord(@JacksonXmlProperty(isAttribute = true) String id, String name) {}
19+
20+
private final XmlMapper MAPPER = newMapper();
21+
22+
@Test
23+
public void testBareAnnotationOnRecord() throws Exception {
24+
String xml = "<SimpleRecord><name>test</name><value>val</value></SimpleRecord>";
25+
SimpleRecord result = MAPPER.readValue(xml, SimpleRecord.class);
26+
assertEquals("test", result.name());
27+
assertEquals("val", result.value());
28+
}
29+
30+
@Test
31+
public void testIsAttributeWithoutLocalNameOnRecord() throws Exception {
32+
String xml = "<AttributeRecord id='123'><name>test</name></AttributeRecord>";
33+
AttributeRecord result = MAPPER.readValue(xml, AttributeRecord.class);
34+
assertEquals("123", result.id());
35+
assertEquals("test", result.name());
36+
}
37+
38+
// Verify serialization round-trip works too
39+
@Test
40+
public void testSerializationRoundTrip() throws Exception {
41+
AttributeRecord original = new AttributeRecord("123", "test");
42+
String xml = MAPPER.writeValueAsString(original);
43+
AttributeRecord result = MAPPER.readValue(xml, AttributeRecord.class);
44+
assertEquals(original.id(), result.id());
45+
assertEquals(original.name(), result.name());
46+
}
47+
}

0 commit comments

Comments
 (0)