diff --git a/src/main/java/com/fasterxml/jackson/dataformat/xml/deser/FromXmlParser.java b/src/main/java/com/fasterxml/jackson/dataformat/xml/deser/FromXmlParser.java index 70a3236f..0dfd7d8f 100644 --- a/src/main/java/com/fasterxml/jackson/dataformat/xml/deser/FromXmlParser.java +++ b/src/main/java/com/fasterxml/jackson/dataformat/xml/deser/FromXmlParser.java @@ -786,8 +786,21 @@ public JsonToken nextToken() throws IOException return _updateToken(JsonToken.START_OBJECT); } if (_parsingContext.inArray()) { - // Yup: in array, so this element could be verified; but it won't be - // reported anyway, and we need to process following event. + // Validate that all array elements have the same element name + String currentElementName = _xmlTokens.getLocalName(); + String expectedElementName = _parsingContext.getExpectedArrayElementName(); + + if (expectedElementName != null) { + // We have an expected name, validate it matches + if (!expectedElementName.equals(currentElementName)) { + _reportError("Unexpected element name '%s' in array; expected '%s'", + currentElementName, expectedElementName); + } + } else { + // First element in array, set as expected name + _parsingContext.setExpectedArrayElementName(currentElementName); + } + token = _nextToken(); _mayBeLeaf = true; continue; diff --git a/src/main/java/com/fasterxml/jackson/dataformat/xml/deser/XmlReadContext.java b/src/main/java/com/fasterxml/jackson/dataformat/xml/deser/XmlReadContext.java index aa40167b..3fe7c1cf 100644 --- a/src/main/java/com/fasterxml/jackson/dataformat/xml/deser/XmlReadContext.java +++ b/src/main/java/com/fasterxml/jackson/dataformat/xml/deser/XmlReadContext.java @@ -51,6 +51,14 @@ public final class XmlReadContext */ protected String _wrappedName; + /** + * Expected element name for array elements. When set, all elements + * in this array context must have this local name. + * + * @since 2.21 + */ + protected String _expectedArrayElementName; + /* /********************************************************************** /* Simple instance reuse slots; speeds up things a bit (10-15%) @@ -99,6 +107,7 @@ protected final void reset(int type, int lineNr, int colNr) _currentName = null; _currentValue = null; _namesToWrap = null; + _expectedArrayElementName = null; if (_dups != null) { _dups.reset(); } @@ -231,6 +240,20 @@ public boolean shouldWrap(String localName) { return (_namesToWrap != null) && _namesToWrap.contains(localName); } + /** + * @since 2.21 + */ + public String getExpectedArrayElementName() { + return _expectedArrayElementName; + } + + /** + * @since 2.21 + */ + public void setExpectedArrayElementName(String name) { + _expectedArrayElementName = name; + } + protected void convertToArray() { _type = TYPE_ARRAY; } diff --git a/src/test/java/com/fasterxml/jackson/dataformat/xml/lists/ListDeser787Test.java b/src/test/java/com/fasterxml/jackson/dataformat/xml/lists/ListDeser787Test.java new file mode 100644 index 00000000..fe0859d9 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/dataformat/xml/lists/ListDeser787Test.java @@ -0,0 +1,95 @@ +package com.fasterxml.jackson.dataformat.xml.lists; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.xml.XmlTestUtil; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlText; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Test for [dataformat-xml#787]: List deserialization should throw an exception + * when encountering mismatched element types (e.g., <bar> elements when + * expecting <foo> in a list) + */ +public class ListDeser787Test extends XmlTestUtil +{ + static class Root787 { + @JacksonXmlProperty + List foos = new ArrayList<>(); + } + + static class Foo787 { + @JacksonXmlText + String text; + + @JacksonXmlProperty(isAttribute = true) + Integer sequenceNr; + + public Foo787() { } + + public Foo787(String text, Integer sequenceNr) { + this.text = text; + this.sequenceNr = sequenceNr; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Foo787 foo = (Foo787) o; + return Objects.equals(text, foo.text) && Objects.equals(sequenceNr, foo.sequenceNr); + } + + @Override + public int hashCode() { + return Objects.hash(text, sequenceNr); + } + + @Override + public String toString() { + return "Foo787{" + + "text='" + text + '\'' + + ", sequenceNr=" + sequenceNr + + '}'; + } + } + + private final ObjectMapper MAPPER = newMapper(); + + // [dataformat-xml#787]: Should throw exception on non-matching elements in list + @Test + public void testDeser787MixedElements() throws Exception + { + String xml = + "\n" + + " \n" + + " somefoo\n" + + " otherfoo\n" + + " something very different\n" + + " \n" + + ""; + + // Should throw exception when encountering element in a list expecting elements + JsonMappingException exception = assertThrows(JsonMappingException.class, () -> { + MAPPER.readValue(xml, Root787.class); + }); + + // Verify the error message mentions the mismatched element names + String message = exception.getMessage(); + assertTrue(message.contains("Unexpected element name 'bar'"), + "Error message should mention 'bar': " + message); + assertTrue(message.contains("expected 'foo'"), + "Error message should mention expected 'foo': " + message); + } +}