Skip to content

Commit 8b1cbb5

Browse files
authored
Merge branch '3.x' into fix/issue-615
2 parents 092a6b9 + 0089f3d commit 8b1cbb5

4 files changed

Lines changed: 175 additions & 2 deletions

File tree

.github/workflows/main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ jobs:
9393
parse_coverage() {
9494
local csv_file=$1 col_missed=$2 col_covered=$3
9595
awk -F',' -v m="$col_missed" -v c="$col_covered" \
96-
'NR>1 && $1=="jackson-dataformat-xml" {
96+
'NR>1 && tolower($1)==tolower("jackson-dataformat-xml") {
9797
total_missed += $m;
9898
total_covered += $c;
9999
}

release-notes/VERSION

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ Version: 3.x (for earlier see VERSION-2.x)
77

88
3.2.0 (not yet released)
99

10-
No changes since 3.1
10+
#802: Serialization with Polymorphisme and `EXTERNAL_PROPERTY` = duplicate property
11+
(reported by @ Adrien-dev25 )
12+
(fix by @cowtowncoder, w/ Claude code)
1113

1214
3.1.0 (23-Feb-2026)
1315

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package tools.jackson.dataformat.xml.ser;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import com.fasterxml.jackson.annotation.*;
6+
7+
import tools.jackson.dataformat.xml.*;
8+
9+
import static org.junit.jupiter.api.Assertions.*;
10+
11+
// [dataformat-xml#802] Test for EXTERNAL_PROPERTY type info duplication
12+
public class PolymorphicExternalTest extends XmlTestUtil
13+
{
14+
static class Cage {
15+
public String id;
16+
public String type;
17+
18+
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type",
19+
include = JsonTypeInfo.As.EXTERNAL_PROPERTY)
20+
@JsonSubTypes({
21+
@JsonSubTypes.Type(value = Cat.class, name = "CAT"),
22+
@JsonSubTypes.Type(value = Dog.class, name = "DOG")
23+
})
24+
public Animal animal;
25+
}
26+
27+
public abstract static class Animal { }
28+
29+
public static class Cat extends Animal {
30+
public String firstName = "My name is cat";
31+
}
32+
33+
public static class Dog extends Animal {
34+
public String lastName = "My name is dog";
35+
}
36+
37+
private final XmlMapper MAPPER = newMapper();
38+
39+
@Test
40+
public void testExternalPropertyNoDuplicate() throws Exception
41+
{
42+
Cage cage = new Cage();
43+
cage.id = "123";
44+
cage.type = "CAT";
45+
cage.animal = new Cat();
46+
47+
String xml = MAPPER.writeValueAsString(cage);
48+
//System.out.println("Serialized XML: " + xml);
49+
50+
// Count occurrences of "<type>" - should be exactly 1
51+
int count = countOccurrences(xml, "<type>");
52+
assertEquals(1, count,
53+
"Expected exactly one <type> element but found " + count + " in: " + xml);
54+
}
55+
56+
@Test
57+
public void testExternalPropertyRoundTrip() throws Exception
58+
{
59+
Cage cage = new Cage();
60+
cage.id = "123";
61+
cage.type = "CAT";
62+
cage.animal = new Cat();
63+
64+
String xml = MAPPER.writeValueAsString(cage);
65+
System.out.println("Serialized XML: " + xml);
66+
67+
// Should be able to round-trip
68+
Cage result = MAPPER.readValue(xml, Cage.class);
69+
assertNotNull(result);
70+
assertEquals("123", result.id);
71+
// Note: with EXTERNAL_PROPERTY, the external type handler consumes
72+
// the "type" element for type resolution, so the bean property is null
73+
// (same behavior as JSON)
74+
assertInstanceOf(Cat.class, result.animal);
75+
assertEquals("My name is cat", ((Cat) result.animal).firstName);
76+
}
77+
78+
private static int countOccurrences(String str, String sub) {
79+
int count = 0;
80+
int idx = 0;
81+
while ((idx = str.indexOf(sub, idx)) != -1) {
82+
count++;
83+
idx += sub.length();
84+
}
85+
return count;
86+
}
87+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package tools.jackson.dataformat.xml.tofix;
2+
3+
import java.util.List;
4+
5+
import org.junit.jupiter.api.Test;
6+
7+
import com.fasterxml.jackson.annotation.JsonCreator;
8+
9+
import tools.jackson.dataformat.xml.XmlTestUtil;
10+
import tools.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
11+
import tools.jackson.dataformat.xml.annotation.JacksonXmlProperty;
12+
import tools.jackson.dataformat.xml.testutil.failure.JacksonTestFailureExpected;
13+
14+
import static org.junit.jupiter.api.Assertions.assertEquals;
15+
import static org.junit.jupiter.api.Assertions.assertNotNull;
16+
17+
// [dataformat-xml#795] Element wrapper not applied with creator-based deserialization
18+
public class ElementWrapperWithCreator795Test extends XmlTestUtil
19+
{
20+
static class HttpHeader {
21+
private String name;
22+
private String value;
23+
24+
protected HttpHeader() { }
25+
public HttpHeader(String name, String value) {
26+
this.name = name;
27+
this.value = value;
28+
}
29+
30+
public String getName() { return name; }
31+
public void setName(String n) { name = n; }
32+
public String getValue() { return value; }
33+
public void setValue(String v) { value = v; }
34+
}
35+
36+
// Creator-based (immutable) class: wrapper + creator triggers name mismatch
37+
static class Config {
38+
@JacksonXmlElementWrapper(localName = "httpHeaders")
39+
@JacksonXmlProperty(localName = "property")
40+
final List<HttpHeader> httpHeaders;
41+
42+
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
43+
public Config(
44+
@JacksonXmlElementWrapper(localName = "httpHeaders")
45+
@JacksonXmlProperty(localName = "property")
46+
List<HttpHeader> httpHeaders)
47+
{
48+
this.httpHeaders = httpHeaders;
49+
}
50+
51+
public List<HttpHeader> getHttpHeaders() { return httpHeaders; }
52+
}
53+
54+
private static final String XML =
55+
"<Config>"
56+
+ "<httpHeaders>"
57+
+ "<property>"
58+
+ "<name>X-JFrog-Art-Api</name>"
59+
+ "<value>myApiToken</value>"
60+
+ "</property>"
61+
+ "</httpHeaders>"
62+
+ "</Config>";
63+
64+
@Test
65+
public void testSerializeWithWrapper() throws Exception
66+
{
67+
Config config = new Config(List.of(new HttpHeader("X-JFrog-Art-Api", "myApiToken")));
68+
String xml = newMapper().writeValueAsString(config);
69+
assertEquals(XML, xml);
70+
}
71+
72+
// Deserialization fails: XmlBeanDeserializerModifier renames property to wrapper name
73+
// ("httpHeaders") but creator property retains original name ("property"), causing mismatch.
74+
@JacksonTestFailureExpected
75+
@Test
76+
public void testDeserializeWithWrapper() throws Exception
77+
{
78+
Config result = newMapper().readValue(XML, Config.class);
79+
assertNotNull(result.getHttpHeaders());
80+
assertEquals(1, result.getHttpHeaders().size());
81+
assertEquals("X-JFrog-Art-Api", result.getHttpHeaders().get(0).getName());
82+
assertEquals("myApiToken", result.getHttpHeaders().get(0).getValue());
83+
}
84+
}

0 commit comments

Comments
 (0)