Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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%)
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Foo787> 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 =
"<root>\n" +
" <foos>\n" +
" <foo sequenceNr=\"1\">somefoo</foo>\n" +
" <foo sequenceNr=\"2\">otherfoo</foo>\n" +
" <bar>something very different</bar>\n" +
" </foos>\n" +
"</root>";

// Should throw exception when encountering <bar> element in a list expecting <foo> 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);
}
}