getHeaderMapRaw() {
return headers.headerMap;
}
+ /**
+ * Gets the number of header columns, which can exceed the header map size when duplicate or null names are allowed.
+ *
+ * @return The number of header columns.
+ */
+ int getHeaderCount() {
+ return headers.headerCount;
+ }
+
/**
* Gets a read-only list of header names that iterates in column order as defined in the CSVFormat's header.
*
diff --git a/src/main/java/org/apache/commons/csv/CSVRecord.java b/src/main/java/org/apache/commons/csv/CSVRecord.java
index 9e8071379..2de2fe315 100644
--- a/src/main/java/org/apache/commons/csv/CSVRecord.java
+++ b/src/main/java/org/apache/commons/csv/CSVRecord.java
@@ -235,8 +235,9 @@ public boolean hasComment() {
* @return true of this record is valid, false if not.
*/
public boolean isConsistent() {
- final Map headerMap = getHeaderMapRaw();
- return headerMap == null || headerMap.size() == values.length;
+ // Compare with the header column count, not the header map size: duplicate or null header names allowed by
+ // DuplicateHeaderMode or allowMissingColumnNames share or lack a map entry.
+ return getHeaderMapRaw() == null || parser.getHeaderCount() == values.length;
}
/**
diff --git a/src/test/java/org/apache/commons/csv/CSVRecordTest.java b/src/test/java/org/apache/commons/csv/CSVRecordTest.java
index 821532975..344ac14c5 100644
--- a/src/test/java/org/apache/commons/csv/CSVRecordTest.java
+++ b/src/test/java/org/apache/commons/csv/CSVRecordTest.java
@@ -194,14 +194,39 @@ void testIsConsistent() {
assertTrue(recordWithHeader.isConsistent());
}
+ /**
+ * A record whose size matches the header column count is consistent even when duplicate header names share a
+ * single header map entry.
+ */
+ @Test
+ void testIsConsistentWithDuplicateHeaderNames() throws IOException {
+ final CSVFormat format = CSVFormat.DEFAULT.builder().setHeader().setDuplicateHeaderMode(DuplicateHeaderMode.ALLOW_ALL).get();
+ try (CSVParser parser = format.parse(new StringReader("a,b,a\n1,2,3\n1,2\n"))) {
+ final List records = parser.getRecords();
+ assertTrue(records.get(0).isConsistent());
+ assertFalse(records.get(1).isConsistent());
+ }
+ }
+
+ /**
+ * A record whose size matches the header column count is consistent even when empty header names share a single
+ * header map entry.
+ */
+ @Test
+ void testIsConsistentWithDuplicateEmptyHeaderNames() throws IOException {
+ final CSVFormat format = CSVFormat.DEFAULT.builder().setHeader().setAllowMissingColumnNames(true)
+ .setDuplicateHeaderMode(DuplicateHeaderMode.ALLOW_EMPTY).get();
+ try (CSVParser parser = format.parse(new StringReader("a,,b,\n1,2,3,4\n"))) {
+ assertTrue(parser.nextRecord().isConsistent());
+ }
+ }
+
@Test
void testIsInconsistent() throws IOException {
- final String[] headers = { "first", "second", "third" };
+ final String[] headers = { "first", "second", "third", "fourth" };
final String rowData = StringUtils.join(values, ',');
try (CSVParser parser = CSVFormat.DEFAULT.withHeader(headers).parse(new StringReader(rowData))) {
- final Map map = parser.getHeaderMapRaw();
final CSVRecord record1 = parser.iterator().next();
- map.put("fourth", Integer.valueOf(4));
assertFalse(record1.isConsistent());
}
}