Skip to content

Commit e9fa1f0

Browse files
committed
Fix #438: prevent quoting of BigInteger/BigDecimal when not expected
1 parent 56686c8 commit e9fa1f0

File tree

5 files changed

+112
-6
lines changed

5 files changed

+112
-6
lines changed

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ public enum Feature
6363
* actually need this.
6464
* Note that this feature has precedence over {@link #STRICT_CHECK_FOR_QUOTING}, when
6565
* both would be applicable.
66+
* Note that this setting does NOT affect quoting of typed values like {@code Number}s
67+
* or {@code Boolean}s.
6668
*
6769
* @since 2.5
6870
*/
@@ -861,7 +863,7 @@ public void writeNumber(BigInteger v) throws IOException
861863
if (!_arraySeparator.isEmpty()) {
862864
_addToArray(String.valueOf(v));
863865
} else {
864-
_writer.write(_columnIndex(), v.toString());
866+
_writer.write(_columnIndex(), v);
865867

866868
}
867869
}
@@ -902,12 +904,11 @@ public void writeNumber(BigDecimal v) throws IOException
902904
}
903905
_verifyValueWrite("write number");
904906
if (!_skipValue) {
905-
String str = isEnabled(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN)
906-
? v.toPlainString() : v.toString();
907+
boolean plain = isEnabled(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN);
907908
if (!_arraySeparator.isEmpty()) {
908-
_addToArray(String.valueOf(v));
909+
_addToArray(plain ? v.toPlainString() : v.toString());
909910
} else {
910-
_writer.write(_columnIndex(), str);
911+
_writer.write(_columnIndex(), v, plain);
911912
}
912913
}
913914
}

csv/src/main/java/com/fasterxml/jackson/dataformat/csv/impl/BufferedValue.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ protected BufferedValue() { }
1616
public static BufferedValue bufferedRaw(String v) { return new RawValue(v); }
1717
public static BufferedValue buffered(int v) { return new IntValue(v); }
1818
public static BufferedValue buffered(long v) { return new LongValue(v); }
19+
public static BufferedValue buffered(float v) { return new FloatValue(v); }
1920
public static BufferedValue buffered(double v) { return new DoubleValue(v); }
21+
public static BufferedValue bufferedNumber(String numStr) { return new BigNumberValue(numStr); }
2022
public static BufferedValue buffered(boolean v) {
2123
return v ? BooleanValue.TRUE : BooleanValue.FALSE;
2224
}
@@ -76,6 +78,19 @@ public void write(CsvEncoder w) throws IOException {
7678
}
7779
}
7880

81+
// @since 2.16
82+
protected final static class FloatValue extends BufferedValue
83+
{
84+
private final float _value;
85+
86+
public FloatValue(float v) { _value = v; }
87+
88+
@Override
89+
public void write(CsvEncoder w) throws IOException {
90+
w.appendValue(_value);
91+
}
92+
}
93+
7994
protected final static class DoubleValue extends BufferedValue
8095
{
8196
private final double _value;
@@ -88,6 +103,18 @@ public void write(CsvEncoder w) throws IOException {
88103
}
89104
}
90105

106+
protected final static class BigNumberValue extends BufferedValue
107+
{
108+
private final String _value;
109+
110+
public BigNumberValue(String v) { _value = v; }
111+
112+
@Override
113+
public void write(CsvEncoder w) throws IOException {
114+
w.appendNumberValue(_value);
115+
}
116+
}
117+
91118
protected final static class BooleanValue extends BufferedValue
92119
{
93120
public final static BooleanValue FALSE = new BooleanValue(false);

csv/src/main/java/com/fasterxml/jackson/dataformat/csv/impl/CsvEncoder.java

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
import java.io.IOException;
1111
import java.io.Writer;
12+
import java.math.BigDecimal;
13+
import java.math.BigInteger;
1214
import java.util.Arrays;
1315

1416
/**
@@ -438,6 +440,19 @@ public final void write(int columnIndex, long value) throws IOException
438440
_buffer(columnIndex, BufferedValue.buffered(value));
439441
}
440442

443+
// @since 2.16
444+
public final void write(int columnIndex, BigInteger value) throws IOException
445+
{
446+
// easy case: all in order
447+
final String numStr = value.toString();
448+
if (columnIndex == _nextColumnToWrite) {
449+
appendNumberValue(numStr);
450+
++_nextColumnToWrite;
451+
return;
452+
}
453+
_buffer(columnIndex, BufferedValue.bufferedNumber(numStr));
454+
}
455+
441456
public final void write(int columnIndex, float value) throws IOException
442457
{
443458
// easy case: all in order
@@ -460,6 +475,20 @@ public final void write(int columnIndex, double value) throws IOException
460475
_buffer(columnIndex, BufferedValue.buffered(value));
461476
}
462477

478+
// @since 2.16
479+
public final void write(int columnIndex, BigDecimal value, boolean plain) throws IOException
480+
{
481+
final String numStr = plain ? value.toPlainString() : value.toString();
482+
483+
// easy case: all in order
484+
if (columnIndex == _nextColumnToWrite) {
485+
appendNumberValue(numStr);
486+
++_nextColumnToWrite;
487+
return;
488+
}
489+
_buffer(columnIndex, BufferedValue.bufferedNumber(numStr));
490+
}
491+
463492
public final void write(int columnIndex, boolean value) throws IOException
464493
{
465494
// easy case: all in order
@@ -599,7 +628,7 @@ protected void appendValue(long value) throws IOException
599628
}
600629
_outputTail = NumberOutput.outputLong(value, _outputBuffer, _outputTail);
601630
}
602-
631+
603632
protected void appendValue(float value) throws IOException
604633
{
605634
String str = NumberOutput.toString(value, _cfgUseFastDoubleWriter);
@@ -626,6 +655,19 @@ protected void appendValue(double value) throws IOException
626655
writeRaw(str);
627656
}
628657

658+
// @since 2.16: pre-encoded BigInteger/BigDecimal value
659+
protected void appendNumberValue(String numValue) throws IOException
660+
{
661+
// Same as "appendRawValue()", except may want quoting
662+
if (_outputTail >= _outputEnd) {
663+
_flushBuffer();
664+
}
665+
if (_nextColumnToWrite > 0) {
666+
appendColumnSeparator();
667+
}
668+
writeRaw(numValue);
669+
}
670+
629671
protected void appendValue(boolean value) throws IOException {
630672
_append(value ? TRUE_CHARS : FALSE_CHARS);
631673
}

csv/src/test/java/com/fasterxml/jackson/dataformat/csv/ser/CSVGeneratorTest.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.io.File;
44
import java.io.StringWriter;
5+
import java.math.BigDecimal;
56

67
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
78

@@ -37,6 +38,19 @@ public Entry2(String id, float amount) {
3738
}
3839
}
3940

41+
@JsonPropertyOrder({"id", "amount", "enabled"})
42+
static class Entry3 {
43+
public String id;
44+
public BigDecimal amount;
45+
public boolean enabled;
46+
47+
public Entry3(String id, BigDecimal amount, boolean enabled) {
48+
this.id = id;
49+
this.amount = amount;
50+
this.enabled = enabled;
51+
}
52+
}
53+
4054
/*
4155
/**********************************************************************
4256
/* Test methods
@@ -209,6 +223,26 @@ public void testForcedQuoting60() throws Exception
209223
assertEquals("xyz,2.5\n", result);
210224
}
211225

226+
// [dataformats-csv#438]: Should not quote BigInteger/BigDecimal (or booleans)
227+
public void testForcedQuotingOfBigDecimal() throws Exception
228+
{
229+
CsvSchema schema = CsvSchema.builder()
230+
.addColumn("id")
231+
.addColumn("amount")
232+
.addColumn("enabled")
233+
.build();
234+
String result = MAPPER.writer(schema)
235+
.with(CsvGenerator.Feature.ALWAYS_QUOTE_STRINGS)
236+
.writeValueAsString(new Entry3("abc", BigDecimal.valueOf(2.5), true));
237+
assertEquals("\"abc\",2.5,true\n", result);
238+
239+
// Also, as per [dataformat-csv#81], should be possible to change dynamically
240+
result = MAPPER.writer(schema)
241+
.without(CsvGenerator.Feature.ALWAYS_QUOTE_STRINGS)
242+
.writeValueAsString(new Entry3("xyz", BigDecimal.valueOf(1.5), false));
243+
assertEquals("xyz,1.5,false\n", result);
244+
}
245+
212246
public void testForcedQuotingWithQuoteEscapedWithBackslash() throws Exception
213247
{
214248
CsvSchema schema = CsvSchema.builder()

release-notes/VERSION-2.x

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ Active Maintainers:
2020
#435: (yaml) Minor parsing validation miss: tagged as `int`, exception
2121
on underscore-only values
2222
#437: (yaml) Update SnakeYAML dependency to 2.2
23+
#438: (csv) `BigInteger` and `BigDecimal` are quoted if
24+
`CsvGenerator.Feature.ALWAYS_QUOTE_STRINGS` enabled
2325

2426
2.15.3 (12-Oct-2023)
2527

0 commit comments

Comments
 (0)