Skip to content

Commit 3180323

Browse files
authored
Fix #495: support value decoration on writing too (#496)
1 parent bb06df1 commit 3180323

File tree

5 files changed

+168
-27
lines changed

5 files changed

+168
-27
lines changed

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

+91-4
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,14 @@ private Feature(boolean defaultState) {
221221
*/
222222
protected int _nextColumnByName = -1;
223223

224+
/**
225+
* Decorator to use for decorating the column value to follow, if any;
226+
* {@code null} if none.
227+
*
228+
* @since 2.18
229+
*/
230+
protected CsvValueDecorator _nextColumnDecorator;
231+
224232
/**
225233
* Flag set when property to write is unknown, and the matching value
226234
* is to be skipped quietly.
@@ -461,14 +469,16 @@ private final void _writeFieldName(String name) throws IOException
461469
if (_skipWithin != null) { // new in 2.7
462470
_skipValue = true;
463471
_nextColumnByName = -1;
472+
_nextColumnDecorator = null;
464473
return;
465474
}
466475
// note: we are likely to get next column name, so pass it as hint
467476
CsvSchema.Column col = _schema.column(name, _nextColumnByName+1);
468477
if (col == null) {
478+
_nextColumnByName = -1;
479+
_nextColumnDecorator = null;
469480
if (isEnabled(JsonGenerator.Feature.IGNORE_UNKNOWN)) {
470481
_skipValue = true;
471-
_nextColumnByName = -1;
472482
return;
473483
}
474484
// not a low-level error, so:
@@ -477,6 +487,7 @@ private final void _writeFieldName(String name) throws IOException
477487
_skipValue = false;
478488
// and all we do is just note index to use for following value write
479489
_nextColumnByName = col.getIndex();
490+
_nextColumnDecorator = col.getValueDecorator();
480491
}
481492

482493
/*
@@ -606,8 +617,13 @@ public final void writeEndArray() throws IOException
606617
return;
607618
}
608619
if (!_arraySeparator.isEmpty()) {
620+
String value = _arrayContents.toString();
621+
// 26-Aug-2024, tatu: [dataformats-text#495] Decorations!
622+
if (_nextColumnDecorator != null) {
623+
value = _nextColumnDecorator.decorateValue(this, value);
624+
}
609625
_arraySeparator = CsvSchema.NO_ARRAY_ELEMENT_SEPARATOR;
610-
_writer.write(_columnIndex(), _arrayContents.toString());
626+
_writer.write(_columnIndex(), value);
611627
}
612628
// 20-Nov-2014, tatu: When doing "untyped"/"raw" output, this means that row
613629
// is now done. But not if writing such an array field, so:
@@ -673,6 +689,10 @@ public void writeString(String text) throws IOException
673689
if (!_arraySeparator.isEmpty()) {
674690
_addToArray(text);
675691
} else {
692+
// 26-Aug-2024, tatu: [dataformats-text#495] Decorations!
693+
if (_nextColumnDecorator != null) {
694+
text = _nextColumnDecorator.decorateValue(this, text);
695+
}
676696
_writer.write(_columnIndex(), text);
677697
}
678698
}
@@ -685,6 +705,11 @@ public void writeString(char[] text, int offset, int len) throws IOException
685705
if (!_skipValue) {
686706
if (!_arraySeparator.isEmpty()) {
687707
_addToArray(new String(text, offset, len));
708+
// 26-Aug-2024, tatu: [dataformats-text#495] Decorations!
709+
} else if (_nextColumnDecorator != null) {
710+
String str = new String(text, offset, len);
711+
_writer.write(_columnIndex(),
712+
_nextColumnDecorator.decorateValue(this, str));
688713
} else {
689714
_writer.write(_columnIndex(), text, offset, len);
690715
}
@@ -699,7 +724,12 @@ public final void writeString(SerializableString sstr) throws IOException
699724
if (!_arraySeparator.isEmpty()) {
700725
_addToArray(sstr.getValue());
701726
} else {
702-
_writer.write(_columnIndex(), sstr.getValue());
727+
// 26-Aug-2024, tatu: [dataformats-text#495] Decorations!
728+
String text = sstr.getValue();
729+
if (_nextColumnDecorator != null) {
730+
text = _nextColumnDecorator.decorateValue(this, text);
731+
}
732+
_writer.write(_columnIndex(), text);
703733
}
704734
}
705735
}
@@ -780,6 +810,7 @@ public void writeBinary(Base64Variant b64variant, byte[] data, int offset, int l
780810
writeNull();
781811
return;
782812
}
813+
783814
_verifyValueWrite("write Binary value");
784815
if (!_skipValue) {
785816
// ok, better just Base64 encode as a String...
@@ -791,6 +822,10 @@ public void writeBinary(Base64Variant b64variant, byte[] data, int offset, int l
791822
if (!_arraySeparator.isEmpty()) {
792823
_addToArray(encoded);
793824
} else {
825+
// 26-Aug-2024, tatu: [dataformats-text#495] Decorations!
826+
if (_nextColumnDecorator != null) {
827+
encoded = _nextColumnDecorator.decorateValue(this, encoded);
828+
}
794829
_writer.write(_columnIndex(), encoded);
795830
}
796831
}
@@ -810,7 +845,13 @@ public void writeBoolean(boolean state) throws IOException
810845
if (!_arraySeparator.isEmpty()) {
811846
_addToArray(state ? "true" : "false");
812847
} else {
813-
_writer.write(_columnIndex(), state);
848+
// 26-Aug-2024, tatu: [dataformats-text#495] Decorations!
849+
if (_nextColumnDecorator != null) {
850+
String text = _nextColumnDecorator.decorateValue(this, state ? "true" : "false");
851+
_writer.write(_columnIndex(), text);
852+
} else {
853+
_writer.write(_columnIndex(), state);
854+
}
814855
}
815856
}
816857
}
@@ -824,6 +865,15 @@ public void writeNull() throws IOException
824865
if (!_arraySeparator.isEmpty()) {
825866
_addToArray(_schema.getNullValueOrEmpty());
826867
} else if (_tokenWriteContext.inObject()) {
868+
// 26-Aug-2024, tatu: [dataformats-text#495] Decorations?
869+
if (_nextColumnDecorator != null) {
870+
String nvl = _nextColumnDecorator.decorateNull(this);
871+
if (nvl != null) {
872+
_writer.write(_columnIndex(), nvl);
873+
return;
874+
}
875+
}
876+
827877
_writer.writeNull(_columnIndex());
828878
} else if (_tokenWriteContext.inArray()) {
829879
// [dataformat-csv#106]: Need to make sure we don't swallow nulls in arrays either
@@ -833,6 +883,14 @@ public void writeNull() throws IOException
833883
// based on either schema property, or CsvGenerator.Feature.
834884
// Note: if nulls are to be written that way, would need to call `finishRow()` right after `writeNull()`
835885
if (!_tokenWriteContext.getParent().inRoot()) {
886+
// 26-Aug-2024, tatu: [dataformats-text#495] Decorations?
887+
if (_nextColumnDecorator != null) {
888+
String nvl = _nextColumnDecorator.decorateNull(this);
889+
if (nvl != null) {
890+
_writer.write(_columnIndex(), nvl);
891+
return;
892+
}
893+
}
836894
_writer.writeNull(_columnIndex());
837895
}
838896

@@ -852,6 +910,10 @@ public void writeNumber(int v) throws IOException
852910
if (!_skipValue) {
853911
if (!_arraySeparator.isEmpty()) {
854912
_addToArray(String.valueOf(v));
913+
// 26-Aug-2024, tatu: [dataformats-text#495] Decorations?
914+
} else if (_nextColumnDecorator != null) {
915+
_writer.write(_columnIndex(),
916+
_nextColumnDecorator.decorateValue(this, String.valueOf(v)));
855917
} else {
856918
_writer.write(_columnIndex(), v);
857919
}
@@ -870,6 +932,10 @@ public void writeNumber(long v) throws IOException
870932
if (!_skipValue) {
871933
if (!_arraySeparator.isEmpty()) {
872934
_addToArray(String.valueOf(v));
935+
// 26-Aug-2024, tatu: [dataformats-text#495] Decorations?
936+
} else if (_nextColumnDecorator != null) {
937+
_writer.write(_columnIndex(),
938+
_nextColumnDecorator.decorateValue(this, String.valueOf(v)));
873939
} else {
874940
_writer.write(_columnIndex(), v);
875941
}
@@ -887,6 +953,10 @@ public void writeNumber(BigInteger v) throws IOException
887953
if (!_skipValue) {
888954
if (!_arraySeparator.isEmpty()) {
889955
_addToArray(String.valueOf(v));
956+
// 26-Aug-2024, tatu: [dataformats-text#495] Decorations?
957+
} else if (_nextColumnDecorator != null) {
958+
_writer.write(_columnIndex(),
959+
_nextColumnDecorator.decorateValue(this, String.valueOf(v)));
890960
} else {
891961
_writer.write(_columnIndex(), v);
892962

@@ -901,6 +971,10 @@ public void writeNumber(double v) throws IOException
901971
if (!_skipValue) {
902972
if (!_arraySeparator.isEmpty()) {
903973
_addToArray(String.valueOf(v));
974+
// 26-Aug-2024, tatu: [dataformats-text#495] Decorations?
975+
} else if (_nextColumnDecorator != null) {
976+
_writer.write(_columnIndex(),
977+
_nextColumnDecorator.decorateValue(this, String.valueOf(v)));
904978
} else {
905979
_writer.write(_columnIndex(), v);
906980
}
@@ -914,6 +988,10 @@ public void writeNumber(float v) throws IOException
914988
if (!_skipValue) {
915989
if (!_arraySeparator.isEmpty()) {
916990
_addToArray(String.valueOf(v));
991+
// 26-Aug-2024, tatu: [dataformats-text#495] Decorations?
992+
} else if (_nextColumnDecorator != null) {
993+
_writer.write(_columnIndex(),
994+
_nextColumnDecorator.decorateValue(this, String.valueOf(v)));
917995
} else {
918996
_writer.write(_columnIndex(), v);
919997
}
@@ -932,6 +1010,11 @@ public void writeNumber(BigDecimal v) throws IOException
9321010
boolean plain = isEnabled(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN);
9331011
if (!_arraySeparator.isEmpty()) {
9341012
_addToArray(plain ? v.toPlainString() : v.toString());
1013+
// 26-Aug-2024, tatu: [dataformats-text#495] Decorations?
1014+
} else if (_nextColumnDecorator != null) {
1015+
String numStr = plain ? v.toPlainString() : v.toString();
1016+
_writer.write(_columnIndex(),
1017+
_nextColumnDecorator.decorateValue(this, numStr));
9351018
} else {
9361019
_writer.write(_columnIndex(), v, plain);
9371020
}
@@ -949,6 +1032,10 @@ public void writeNumber(String encodedValue) throws IOException
9491032
if (!_skipValue) {
9501033
if (!_arraySeparator.isEmpty()) {
9511034
_addToArray(encodedValue);
1035+
// 26-Aug-2024, tatu: [dataformats-text#495] Decorations?
1036+
} else if (_nextColumnDecorator != null) {
1037+
_writer.write(_columnIndex(),
1038+
_nextColumnDecorator.decorateValue(this, encodedValue));
9521039
} else {
9531040
_writer.write(_columnIndex(), encodedValue);
9541041
}

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

+24-1
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,37 @@ public interface CsvValueDecorator
2626
* @param gen Generator that will be used for actual serialization
2727
* @param plainValue Value to decorate
2828
*
29-
* @return Decorated value (which may be {@code plainValue} as-is)
29+
* @return Decorated value (which may be {@code plainValue} as-is) but
30+
* Must Not be {@code null}
3031
*
3132
* @throws IOException if attempt to decorate the value somehow fails
3233
* (typically a {@link com.fasterxml.jackson.core.exc.StreamWriteException})
3334
*/
3435
public String decorateValue(CsvGenerator gen, String plainValue)
3536
throws IOException;
3637

38+
/**
39+
* Method called instead of {@link #decorateValue} in case where value being
40+
* written is from Java {@code null} value: this is often left as-is, without
41+
* decoration (and this is the default implementation), but may be
42+
* decorated.
43+
* To let default Null Value Replacement be used, should return {@code null}:
44+
* this is the default implementation.
45+
*
46+
* @param gen Generator that will be used for actual serialization
47+
*
48+
* @return Decorated value to use, IF NOT {@code null}: if {@code null} will use
49+
* default null replacement value.
50+
*
51+
* @throws IOException if attempt to decorate the value somehow fails
52+
* (typically a {@link com.fasterxml.jackson.core.exc.StreamWriteException})
53+
*/
54+
public default String decorateNull(CsvGenerator gen)
55+
throws IOException
56+
{
57+
return null;
58+
}
59+
3760
/**
3861
* Method called during deserialization, to remove possible decoration
3962
* applied with {@link #decorateValue}.

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

+17-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,23 @@ public class CsvValueDecorators
2525
*/
2626
public final static CsvValueDecorator STRICT_BRACKETS_DECORATOR
2727
= new StringPrefixSuffixDecorator("[", "]", false);
28-
28+
29+
/**
30+
* Factory method for constructing a {@link StringPrefixSuffixDecorator} with
31+
* given prefix and suffix, both optional.
32+
*/
33+
public static CsvValueDecorator optionalPrefixSuffixDecorator(String prefix, String suffix) {
34+
return new StringPrefixSuffixDecorator(prefix, suffix, true);
35+
}
36+
37+
/**
38+
* Factory method for constructing a {@link StringPrefixSuffixDecorator} with
39+
* given prefix and suffix, both required.
40+
*/
41+
public static CsvValueDecorator requiredPrefixSuffixDecorator(String prefix, String suffix) {
42+
return new StringPrefixSuffixDecorator(prefix, suffix, false);
43+
}
44+
2945
/**
3046
* Decorated that adds static prefix and suffix around value to decorate value;
3147
* removes the same when un-decorating. Handling of the case where decoration

0 commit comments

Comments
 (0)