Skip to content
This repository was archived by the owner on Jan 22, 2019. It is now read-only.

Commit 2bcdcb5

Browse files
committed
Fix #95
1 parent 1833512 commit 2bcdcb5

File tree

3 files changed

+115
-18
lines changed

3 files changed

+115
-18
lines changed

release-notes/VERSION

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ Project: jackson-dataformat-csv
1313
#92: Allow multi-character separator values
1414
(contributed by dharaburda@github)
1515
#94: Change schema/mapping related `JsonParseException`s to proper `JsonMappingException`s
16+
#95: Add `CsvParser.Feature.IGNORE_TRAILING_UNMAPPABLE` to allow skipping of
17+
all extra, unmappable columns
1618
#97: Verify CSV headers are in the order as expected (added `strictHeaders` property in `CsvSchema`)
1719
(contributed by Nick B)
1820
#103: `JsonGenerator.Feature.IGNORE_UNKNOWN` does not prevent error when writing structured values

src/main/java/com/fasterxml/jackson/dataformat/csv/CsvParser.java

+37-18
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public enum Feature
4747
/**
4848
* Feature that determines how stream of records (usually CSV lines, but sometimes
4949
* multiple lines when linefeeds are included in quoted values) is exposed:
50-
* either as a sequence of Objects (false), or as an array of Objects (true).
50+
* either as a sequence of Objects (false), or as an Array of Objects (true).
5151
* Using stream of Objects is convenient when using
5252
* <code>ObjectMapper.readValues(...)</code>
5353
* and array of Objects convenient when binding to <code>List</code>s or
@@ -56,7 +56,17 @@ public enum Feature
5656
* Default value is false, meaning that by default a CSV document is exposed as
5757
* a sequence of root-level Object entries.
5858
*/
59-
WRAP_AS_ARRAY(false)
59+
WRAP_AS_ARRAY(false),
60+
61+
/**
62+
* Feature that allows ignoring of unmappable "extra" columns; that is, values for
63+
* columns that appear after columns for which types are defined. When disabled,
64+
* an exception is thrown for such column values, but if enabled, they are
65+
* silently ignored.
66+
*
67+
* @since 2.7
68+
*/
69+
IGNORE_TRAILING_UNMAPPABLE(false),
6070
;
6171

6272
final boolean _defaultState;
@@ -516,17 +526,7 @@ public JsonToken nextToken() throws IOException
516526
return (_currToken = _handleArrayValue());
517527
case STATE_SKIP_EXTRA_COLUMNS:
518528
// Need to just skip whatever remains
519-
_state = STATE_RECORD_START;
520-
while (_reader.nextString() != null) { }
521-
522-
// But once we hit the end of the logical line, get out
523-
// NOTE: seems like we should always be within Object, but let's be conservative
524-
// and check just in case
525-
_parsingContext = _parsingContext.getParent();
526-
_state = _reader.startNewLine() ? STATE_RECORD_START : STATE_DOC_END;
527-
return (_currToken = _parsingContext.inArray()
528-
? JsonToken.END_ARRAY : JsonToken.END_OBJECT);
529-
529+
return _skipUntilEndOfLine();
530530
case STATE_DOC_END:
531531
_reader.close();
532532
if (_parsingContext.inRoot()) {
@@ -690,14 +690,18 @@ protected JsonToken _handleNextEntry() throws IOException
690690
}
691691
return JsonToken.END_OBJECT;
692692
}
693-
_state = STATE_NAMED_VALUE;
694693
_currentValue = next;
695694
if (_columnIndex >= _columnCount) {
696695
_currentName = null;
697-
/* 14-Mar-2012, tatu: As per [Issue-1], let's allow one specific
698-
* case of extra: if we get just one all-whitespace entry, that
699-
* can be just skipped
700-
*/
696+
697+
// 09-Jan-2016, tatu: With [dataformat-csv#95], this may actually be just fine
698+
if (Feature.IGNORE_TRAILING_UNMAPPABLE.enabledIn(_formatFeatures)) {
699+
_state = STATE_SKIP_EXTRA_COLUMNS;
700+
return _skipUntilEndOfLine();
701+
}
702+
703+
// 14-Mar-2012, tatu: As per [dataformat-csv#1], let's allow one specific case
704+
// of extra: if we get just one all-whitespace entry, that can be just skipped
701705
if (_columnIndex == _columnCount) {
702706
next = next.trim();
703707
if (next.length() == 0) {
@@ -707,10 +711,12 @@ protected JsonToken _handleNextEntry() throws IOException
707711
return _handleNextEntryExpectEOL();
708712
}
709713
}
714+
710715
// 21-May-2015, tatu: Need to enter recovery mode, to skip remainder of the line
711716
_state = STATE_SKIP_EXTRA_COLUMNS;
712717
_reportMappingError("Too many entries: expected at most "+_columnCount+" (value #"+_columnCount+" ("+next.length()+" chars) \""+next+"\")");
713718
}
719+
_state = STATE_NAMED_VALUE;
714720
_currentName = _schema.columnName(_columnIndex);
715721
return JsonToken.FIELD_NAME;
716722
}
@@ -889,6 +895,19 @@ protected void _readHeaderLine() throws IOException {
889895
setSchema(builder.build());
890896
}
891897

898+
protected final JsonToken _skipUntilEndOfLine() throws IOException
899+
{
900+
while (_reader.nextString() != null) { }
901+
902+
// But once we hit the end of the logical line, get out
903+
// NOTE: seems like we should always be within Object, but let's be conservative
904+
// and check just in case
905+
_parsingContext = _parsingContext.getParent();
906+
_state = _reader.startNewLine() ? STATE_RECORD_START : STATE_DOC_END;
907+
return (_currToken = _parsingContext.inArray()
908+
? JsonToken.END_ARRAY : JsonToken.END_OBJECT);
909+
}
910+
892911
/*
893912
/**********************************************************
894913
/* String value handling
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package com.fasterxml.jackson.dataformat.csv.deser;
2+
3+
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
4+
5+
import com.fasterxml.jackson.databind.JsonMappingException;
6+
import com.fasterxml.jackson.databind.MappingIterator;
7+
8+
import com.fasterxml.jackson.dataformat.csv.*;
9+
10+
/**
11+
* Test(s) for [dataformat-csv#95]
12+
*/
13+
public class IgnoreUnmappableTest extends ModuleTestBase
14+
{
15+
final CsvMapper MAPPER = mapperForCsv();
16+
17+
@JsonPropertyOrder({ "first", "second" })
18+
static class StringPair {
19+
public String first, second;
20+
}
21+
22+
public void testSimpleIgnoral() throws Exception
23+
{
24+
final String INPUT = "a,b,c,foo\nd,e\nf,g,h,i\n";
25+
final CsvSchema schema = MAPPER.schemaFor(StringPair.class);
26+
27+
// first: throw exception(s) with default settings
28+
MappingIterator<StringPair> it = MAPPER.readerFor(StringPair.class)
29+
.with(schema)
30+
.without(CsvParser.Feature.IGNORE_TRAILING_UNMAPPABLE)
31+
.readValues(INPUT);
32+
33+
try {
34+
it.nextValue();
35+
fail("Should not have passed");
36+
} catch (JsonMappingException e) {
37+
verifyException(e, "Too many entries");
38+
}
39+
40+
// yet second one ought to work
41+
StringPair pair = it.nextValue();
42+
assertEquals("d", pair.first);
43+
assertEquals("e", pair.second);
44+
45+
// and not third, again
46+
try {
47+
it.nextValue();
48+
fail("Should not have passed");
49+
} catch (JsonMappingException e) {
50+
verifyException(e, "Too many entries");
51+
}
52+
it.close();
53+
54+
// But with settings...
55+
it = MAPPER.readerFor(StringPair.class)
56+
.with(schema)
57+
.with(CsvParser.Feature.IGNORE_TRAILING_UNMAPPABLE)
58+
.readValues(INPUT);
59+
60+
pair = it.nextValue();
61+
assertEquals("a", pair.first);
62+
assertEquals("b", pair.second);
63+
64+
pair = it.nextValue();
65+
assertEquals("d", pair.first);
66+
assertEquals("e", pair.second);
67+
68+
pair = it.nextValue();
69+
assertEquals("f", pair.first);
70+
assertEquals("g", pair.second);
71+
72+
assertFalse(it.hasNextValue());
73+
74+
it.close();
75+
}
76+
}

0 commit comments

Comments
 (0)