Skip to content

Commit

Permalink
Fix #495: support value decoration on writing too (#496)
Browse files Browse the repository at this point in the history
  • Loading branch information
cowtowncoder authored Aug 27, 2024
1 parent bb06df1 commit 3180323
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,14 @@ private Feature(boolean defaultState) {
*/
protected int _nextColumnByName = -1;

/**
* Decorator to use for decorating the column value to follow, if any;
* {@code null} if none.
*
* @since 2.18
*/
protected CsvValueDecorator _nextColumnDecorator;

/**
* Flag set when property to write is unknown, and the matching value
* is to be skipped quietly.
Expand Down Expand Up @@ -461,14 +469,16 @@ private final void _writeFieldName(String name) throws IOException
if (_skipWithin != null) { // new in 2.7
_skipValue = true;
_nextColumnByName = -1;
_nextColumnDecorator = null;
return;
}
// note: we are likely to get next column name, so pass it as hint
CsvSchema.Column col = _schema.column(name, _nextColumnByName+1);
if (col == null) {
_nextColumnByName = -1;
_nextColumnDecorator = null;
if (isEnabled(JsonGenerator.Feature.IGNORE_UNKNOWN)) {
_skipValue = true;
_nextColumnByName = -1;
return;
}
// not a low-level error, so:
Expand All @@ -477,6 +487,7 @@ private final void _writeFieldName(String name) throws IOException
_skipValue = false;
// and all we do is just note index to use for following value write
_nextColumnByName = col.getIndex();
_nextColumnDecorator = col.getValueDecorator();
}

/*
Expand Down Expand Up @@ -606,8 +617,13 @@ public final void writeEndArray() throws IOException
return;
}
if (!_arraySeparator.isEmpty()) {
String value = _arrayContents.toString();
// 26-Aug-2024, tatu: [dataformats-text#495] Decorations!
if (_nextColumnDecorator != null) {
value = _nextColumnDecorator.decorateValue(this, value);
}
_arraySeparator = CsvSchema.NO_ARRAY_ELEMENT_SEPARATOR;
_writer.write(_columnIndex(), _arrayContents.toString());
_writer.write(_columnIndex(), value);
}
// 20-Nov-2014, tatu: When doing "untyped"/"raw" output, this means that row
// is now done. But not if writing such an array field, so:
Expand Down Expand Up @@ -673,6 +689,10 @@ public void writeString(String text) throws IOException
if (!_arraySeparator.isEmpty()) {
_addToArray(text);
} else {
// 26-Aug-2024, tatu: [dataformats-text#495] Decorations!
if (_nextColumnDecorator != null) {
text = _nextColumnDecorator.decorateValue(this, text);
}
_writer.write(_columnIndex(), text);
}
}
Expand All @@ -685,6 +705,11 @@ public void writeString(char[] text, int offset, int len) throws IOException
if (!_skipValue) {
if (!_arraySeparator.isEmpty()) {
_addToArray(new String(text, offset, len));
// 26-Aug-2024, tatu: [dataformats-text#495] Decorations!
} else if (_nextColumnDecorator != null) {
String str = new String(text, offset, len);
_writer.write(_columnIndex(),
_nextColumnDecorator.decorateValue(this, str));
} else {
_writer.write(_columnIndex(), text, offset, len);
}
Expand All @@ -699,7 +724,12 @@ public final void writeString(SerializableString sstr) throws IOException
if (!_arraySeparator.isEmpty()) {
_addToArray(sstr.getValue());
} else {
_writer.write(_columnIndex(), sstr.getValue());
// 26-Aug-2024, tatu: [dataformats-text#495] Decorations!
String text = sstr.getValue();
if (_nextColumnDecorator != null) {
text = _nextColumnDecorator.decorateValue(this, text);
}
_writer.write(_columnIndex(), text);
}
}
}
Expand Down Expand Up @@ -780,6 +810,7 @@ public void writeBinary(Base64Variant b64variant, byte[] data, int offset, int l
writeNull();
return;
}

_verifyValueWrite("write Binary value");
if (!_skipValue) {
// ok, better just Base64 encode as a String...
Expand All @@ -791,6 +822,10 @@ public void writeBinary(Base64Variant b64variant, byte[] data, int offset, int l
if (!_arraySeparator.isEmpty()) {
_addToArray(encoded);
} else {
// 26-Aug-2024, tatu: [dataformats-text#495] Decorations!
if (_nextColumnDecorator != null) {
encoded = _nextColumnDecorator.decorateValue(this, encoded);
}
_writer.write(_columnIndex(), encoded);
}
}
Expand All @@ -810,7 +845,13 @@ public void writeBoolean(boolean state) throws IOException
if (!_arraySeparator.isEmpty()) {
_addToArray(state ? "true" : "false");
} else {
_writer.write(_columnIndex(), state);
// 26-Aug-2024, tatu: [dataformats-text#495] Decorations!
if (_nextColumnDecorator != null) {
String text = _nextColumnDecorator.decorateValue(this, state ? "true" : "false");
_writer.write(_columnIndex(), text);
} else {
_writer.write(_columnIndex(), state);
}
}
}
}
Expand All @@ -824,6 +865,15 @@ public void writeNull() throws IOException
if (!_arraySeparator.isEmpty()) {
_addToArray(_schema.getNullValueOrEmpty());
} else if (_tokenWriteContext.inObject()) {
// 26-Aug-2024, tatu: [dataformats-text#495] Decorations?
if (_nextColumnDecorator != null) {
String nvl = _nextColumnDecorator.decorateNull(this);
if (nvl != null) {
_writer.write(_columnIndex(), nvl);
return;
}
}

_writer.writeNull(_columnIndex());
} else if (_tokenWriteContext.inArray()) {
// [dataformat-csv#106]: Need to make sure we don't swallow nulls in arrays either
Expand All @@ -833,6 +883,14 @@ public void writeNull() throws IOException
// based on either schema property, or CsvGenerator.Feature.
// Note: if nulls are to be written that way, would need to call `finishRow()` right after `writeNull()`
if (!_tokenWriteContext.getParent().inRoot()) {
// 26-Aug-2024, tatu: [dataformats-text#495] Decorations?
if (_nextColumnDecorator != null) {
String nvl = _nextColumnDecorator.decorateNull(this);
if (nvl != null) {
_writer.write(_columnIndex(), nvl);
return;
}
}
_writer.writeNull(_columnIndex());
}

Expand All @@ -852,6 +910,10 @@ public void writeNumber(int v) throws IOException
if (!_skipValue) {
if (!_arraySeparator.isEmpty()) {
_addToArray(String.valueOf(v));
// 26-Aug-2024, tatu: [dataformats-text#495] Decorations?
} else if (_nextColumnDecorator != null) {
_writer.write(_columnIndex(),
_nextColumnDecorator.decorateValue(this, String.valueOf(v)));
} else {
_writer.write(_columnIndex(), v);
}
Expand All @@ -870,6 +932,10 @@ public void writeNumber(long v) throws IOException
if (!_skipValue) {
if (!_arraySeparator.isEmpty()) {
_addToArray(String.valueOf(v));
// 26-Aug-2024, tatu: [dataformats-text#495] Decorations?
} else if (_nextColumnDecorator != null) {
_writer.write(_columnIndex(),
_nextColumnDecorator.decorateValue(this, String.valueOf(v)));
} else {
_writer.write(_columnIndex(), v);
}
Expand All @@ -887,6 +953,10 @@ public void writeNumber(BigInteger v) throws IOException
if (!_skipValue) {
if (!_arraySeparator.isEmpty()) {
_addToArray(String.valueOf(v));
// 26-Aug-2024, tatu: [dataformats-text#495] Decorations?
} else if (_nextColumnDecorator != null) {
_writer.write(_columnIndex(),
_nextColumnDecorator.decorateValue(this, String.valueOf(v)));
} else {
_writer.write(_columnIndex(), v);

Expand All @@ -901,6 +971,10 @@ public void writeNumber(double v) throws IOException
if (!_skipValue) {
if (!_arraySeparator.isEmpty()) {
_addToArray(String.valueOf(v));
// 26-Aug-2024, tatu: [dataformats-text#495] Decorations?
} else if (_nextColumnDecorator != null) {
_writer.write(_columnIndex(),
_nextColumnDecorator.decorateValue(this, String.valueOf(v)));
} else {
_writer.write(_columnIndex(), v);
}
Expand All @@ -914,6 +988,10 @@ public void writeNumber(float v) throws IOException
if (!_skipValue) {
if (!_arraySeparator.isEmpty()) {
_addToArray(String.valueOf(v));
// 26-Aug-2024, tatu: [dataformats-text#495] Decorations?
} else if (_nextColumnDecorator != null) {
_writer.write(_columnIndex(),
_nextColumnDecorator.decorateValue(this, String.valueOf(v)));
} else {
_writer.write(_columnIndex(), v);
}
Expand All @@ -932,6 +1010,11 @@ public void writeNumber(BigDecimal v) throws IOException
boolean plain = isEnabled(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN);
if (!_arraySeparator.isEmpty()) {
_addToArray(plain ? v.toPlainString() : v.toString());
// 26-Aug-2024, tatu: [dataformats-text#495] Decorations?
} else if (_nextColumnDecorator != null) {
String numStr = plain ? v.toPlainString() : v.toString();
_writer.write(_columnIndex(),
_nextColumnDecorator.decorateValue(this, numStr));
} else {
_writer.write(_columnIndex(), v, plain);
}
Expand All @@ -949,6 +1032,10 @@ public void writeNumber(String encodedValue) throws IOException
if (!_skipValue) {
if (!_arraySeparator.isEmpty()) {
_addToArray(encodedValue);
// 26-Aug-2024, tatu: [dataformats-text#495] Decorations?
} else if (_nextColumnDecorator != null) {
_writer.write(_columnIndex(),
_nextColumnDecorator.decorateValue(this, encodedValue));
} else {
_writer.write(_columnIndex(), encodedValue);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,37 @@ public interface CsvValueDecorator
* @param gen Generator that will be used for actual serialization
* @param plainValue Value to decorate
*
* @return Decorated value (which may be {@code plainValue} as-is)
* @return Decorated value (which may be {@code plainValue} as-is) but
* Must Not be {@code null}
*
* @throws IOException if attempt to decorate the value somehow fails
* (typically a {@link com.fasterxml.jackson.core.exc.StreamWriteException})
*/
public String decorateValue(CsvGenerator gen, String plainValue)
throws IOException;

/**
* Method called instead of {@link #decorateValue} in case where value being
* written is from Java {@code null} value: this is often left as-is, without
* decoration (and this is the default implementation), but may be
* decorated.
* To let default Null Value Replacement be used, should return {@code null}:
* this is the default implementation.
*
* @param gen Generator that will be used for actual serialization
*
* @return Decorated value to use, IF NOT {@code null}: if {@code null} will use
* default null replacement value.
*
* @throws IOException if attempt to decorate the value somehow fails
* (typically a {@link com.fasterxml.jackson.core.exc.StreamWriteException})
*/
public default String decorateNull(CsvGenerator gen)
throws IOException
{
return null;
}

/**
* Method called during deserialization, to remove possible decoration
* applied with {@link #decorateValue}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,23 @@ public class CsvValueDecorators
*/
public final static CsvValueDecorator STRICT_BRACKETS_DECORATOR
= new StringPrefixSuffixDecorator("[", "]", false);


/**
* Factory method for constructing a {@link StringPrefixSuffixDecorator} with
* given prefix and suffix, both optional.
*/
public static CsvValueDecorator optionalPrefixSuffixDecorator(String prefix, String suffix) {
return new StringPrefixSuffixDecorator(prefix, suffix, true);
}

/**
* Factory method for constructing a {@link StringPrefixSuffixDecorator} with
* given prefix and suffix, both required.
*/
public static CsvValueDecorator requiredPrefixSuffixDecorator(String prefix, String suffix) {
return new StringPrefixSuffixDecorator(prefix, suffix, false);
}

/**
* Decorated that adds static prefix and suffix around value to decorate value;
* removes the same when un-decorating. Handling of the case where decoration
Expand Down
Loading

0 comments on commit 3180323

Please sign in to comment.