Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add serialization of object members for csv. #97

Open
wants to merge 2 commits into
base: 2.9
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package com.fasterxml.jackson.dataformat.csv;

import java.util.Arrays;

/**
* @author Petar Tahchiev
* @since 2.0.1
*/
public class CsvEscapes {
private final static char[] HEX = "0123456789ABCDEF".toCharArray();

private final static int UNICODE_ESCAPE = -1;

private final static int[] sValueEscapes;
static {
final int[] table = new int[256];
// For values, fewer escapes needed, but most control chars need them
for (int i = 0; i < 32; ++i) {
table[i] = UNICODE_ESCAPE;
// also high-bit ones
table[128+i] = UNICODE_ESCAPE;
}
// also, that one weird character needs escaping:
table[0x7F] = UNICODE_ESCAPE;

// except for "well-known" ones
table['\t'] = 't';
table['\r'] = 'r';
table['\n'] = 'n';

// Beyond that, just backslash
table['\\'] = '\\';
sValueEscapes = table;
}

private final static int[] sKeyEscapes;
static {
// with keys, start with value escapes, and add the rest
final int[] table = Arrays.copyOf(sValueEscapes, 256);

// comment line starters (could get by with just start char but whatever)
table['#'] = '#';
table['!'] = '!';
// and then equals (and equivalents) that mark end of key
table['='] = '=';
table[':'] = ':';
// plus space chars are escapes too
table[' '] = ' ';

sKeyEscapes = table;
}

public static void appendKey(StringBuilder sb, String key) {
final int end = key.length();
if (end == 0) {
return;
}
final int[] esc = sKeyEscapes;
// first quick loop for common case of no escapes
int i = 0;

while (true) {
char c = key.charAt(i);
if ((c > 0xFF) || esc[c] != 0) {
break;
}
sb.append(c);
if (++i == end) {
return;
}
}
_appendWithEscapes(sb, key, esc, i);
}

private static void _appendWithEscapes(StringBuilder sb, String key,
int[] esc, int i)
{
final int end = key.length();
do {
char c = key.charAt(i);
int type = (c > 0xFF) ? UNICODE_ESCAPE : esc[c];
if (type == 0) {
sb.append(c);
continue;
}
if (type == UNICODE_ESCAPE) {
sb.append('\\');
sb.append('u');
sb.append(HEX[c >>> 12]);
sb.append(HEX[(c >> 8) & 0xF]);
sb.append(HEX[(c >> 4) & 0xF]);
sb.append(HEX[c & 0xF]);
} else {
sb.append('\\');
sb.append((char) type);
}
} while (++i < end);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ private Feature(boolean defaultState) {
*/
protected int _formatFeatures;

protected final StringBuilder _basePath = new StringBuilder(50);

protected int _indentLength;

/**
* Definition of columns being written, if available.
*/
Expand All @@ -146,6 +150,12 @@ private Feature(boolean defaultState) {
// note: can not be final since we may need to re-create it for new schema
protected CsvEncoder _writer;

/**
* Current context, in form we can use it (GeneratorBase has
* untyped reference; left as null)
*/
protected CsvWriteContext _writeContext;

/*
/**********************************************************
/* Output state
Expand Down Expand Up @@ -201,7 +211,7 @@ private Feature(boolean defaultState) {
*
* @since 2.7
*/
protected JsonWriteContext _skipWithin;
protected CsvWriteContext _skipWithin;

/*
/**********************************************************
Expand All @@ -220,6 +230,7 @@ public CsvGenerator(IOContext ctxt, int jsonFeatures, int csvFeatures,
_formatFeatures = csvFeatures;
_schema = schema;
_writer = new CsvEncoder(ctxt, csvFeatures, out, schema);
_writeContext = CsvWriteContext.createRootContext();
}

public CsvGenerator(IOContext ctxt, int jsonFeatures, int csvFeatures,
Expand All @@ -229,6 +240,7 @@ public CsvGenerator(IOContext ctxt, int jsonFeatures, int csvFeatures,
_ioContext = ctxt;
_formatFeatures = csvFeatures;
_writer = csvWriter;
_writeContext = CsvWriteContext.createRootContext();
}

/*
Expand Down Expand Up @@ -281,12 +293,21 @@ public int getOutputBuffered() {
return _writer.getOutputBuffered();
}

@Override
public CsvWriteContext getOutputContext() {
return _writeContext;
}

@Override
public void setSchema(FormatSchema schema)
{
if (schema instanceof CsvSchema) {
if (_schema != schema) {
_schema = (CsvSchema) schema;
if (_writeContext.inRoot()) {
_basePath.setLength(0);
}

_writer = _writer.withSchema(_schema);
}
} else {
Expand Down Expand Up @@ -345,7 +366,7 @@ public boolean canOmitFields() {
@Override
public final void writeFieldName(String name) throws IOException
{
if (_writeContext.writeFieldName(name) == JsonWriteContext.STATUS_EXPECT_VALUE) {
if (!_writeContext.writeFieldName(name)) {
_reportError("Can not write a field name, expecting a value");
}
_writeFieldName(name);
Expand All @@ -355,7 +376,7 @@ public final void writeFieldName(String name) throws IOException
public final void writeFieldName(SerializableString name) throws IOException
{
// Object is a value, need to verify it's allowed
if (_writeContext.writeFieldName(name.getValue()) == JsonWriteContext.STATUS_EXPECT_VALUE) {
if (!_writeContext.writeFieldName(name.getValue())) {
_reportError("Can not write a field name, expecting a value");
}
_writeFieldName(name.getValue());
Expand All @@ -364,7 +385,7 @@ public final void writeFieldName(SerializableString name) throws IOException
@Override
public final void writeStringField(String fieldName, String value) throws IOException
{
if (_writeContext.writeFieldName(fieldName) == JsonWriteContext.STATUS_EXPECT_VALUE) {
if (!_writeContext.writeFieldName(fieldName)) {
_reportError("Can not write a field name, expecting a value");
}
_writeFieldName(fieldName);
Expand All @@ -378,16 +399,36 @@ private final void _writeFieldName(String name) throws IOException
// not a low-level error, so:
_reportMappingError("Unrecognized column '"+name+"', can not resolve without CsvSchema");
}
if (_skipWithin != null) { // new in 2.7
_skipValue = true;
_nextColumnByName = -1;
return;
// if (_skipWithin != null) { // new in 2.7
// _skipValue = true;
// _nextColumnByName = -1;
// return;
// }

boolean internal = false;
_writeContext.truncatePath(_basePath);
if (_basePath.length() > _indentLength) {
String sep = _schema.pathSeparator();
if (!sep.isEmpty()) {
_basePath.append(sep);
internal = true;
}
}

// note: we are likely to get next column name, so pass it as hint
CsvEscapes.appendKey(_basePath, name);

name = _basePath.toString();

CsvSchema.Column col = _schema.column(name, _nextColumnByName+1);
if (col == null) {
if (isEnabled(JsonGenerator.Feature.IGNORE_UNKNOWN)) {
_skipValue = true;
if (internal) {
_skipWithin = _writeContext;
_skipValue = true;
} else {
_skipValue = true;
}
_nextColumnByName = -1;
return;
}
Expand All @@ -397,6 +438,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();

}

/*
Expand Down Expand Up @@ -499,7 +541,7 @@ && _skipValue && isEnabled(JsonGenerator.Feature.IGNORE_UNKNOWN)) {
_reportError("CSV generator does not support nested Array values");
}
}
_writeContext = _writeContext.createChildArrayContext();
_writeContext = _writeContext.createChildArrayContext(_basePath.length());
// and that's about it, really
}

Expand Down Expand Up @@ -534,32 +576,35 @@ public final void writeStartObject() throws IOException
_verifyValueWrite("start an object");
// No nesting for objects; can write Objects inside logical root-level arrays.
// 14-Dec-2015, tatu: ... except, should be fine if we are ignoring the property
if (_writeContext.inObject() ||
// 07-Nov-2017, tatu: But we may actually be nested indirectly; so check
(_writeContext.inArray() && !_writeContext.getParent().inRoot())) {
if (_skipWithin == null) { // new in 2.7
if (_skipValue && isEnabled(JsonGenerator.Feature.IGNORE_UNKNOWN)) {
_skipWithin = _writeContext;
} else {
_reportMappingError("CSV generator does not support Object values for properties (nested Objects)");
}
}
}
_writeContext = _writeContext.createChildObjectContext();
// if (_writeContext.inObject() ||
// // 07-Nov-2017, tatu: But we may actually be nested indirectly; so check
// (_writeContext.inArray() && !_writeContext.getParent().inRoot())) {
// if (_skipWithin == null) { // new in 2.7
// if (_skipValue && isEnabled(JsonGenerator.Feature.IGNORE_UNKNOWN)) {
// _skipWithin = _writeContext;
// } else {
//
// _reportMappingError("CSV generator does not support Object values for properties (nested Objects)");
// }
// }
// }
_writeContext = _writeContext.createChildObjectContext(_basePath.length());
}

@Override
public final void writeEndObject() throws IOException
{
if (!_writeContext.inObject()) {
_reportError("Current context not Object but "+_writeContext.typeDesc());
_reportError("Current context not Object but " + _writeContext.typeDesc());
}
CsvWriteContext _origContext = _writeContext;
_writeContext = _writeContext.getParent();
// 14-Dec-2015, tatu: To complete skipping of ignored structured value, need this:
if (_skipWithin != null) {
if (_writeContext == _skipWithin) {
if (_writeContext == _skipWithin || _origContext == _skipWithin) {
_skipWithin = null;
}

return;
}
// not 100% fool-proof, but chances are row should be done now
Expand Down Expand Up @@ -883,7 +928,7 @@ public void writeOmittedField(String fieldName) throws IOException
// assumed to have been removed from schema too
} else {
// basically combination of "writeFieldName()" and "writeNull()"
if (_writeContext.writeFieldName(fieldName) == JsonWriteContext.STATUS_EXPECT_VALUE) {
if (!_writeContext.writeFieldName(fieldName)) {
_reportError("Can not skip a field, expecting a value");
}
// and all we do is just note index to use for following value write
Expand All @@ -903,8 +948,7 @@ public void writeOmittedField(String fieldName) throws IOException
@Override
protected final void _verifyValueWrite(String typeMsg) throws IOException
{
int status = _writeContext.writeValue();
if (status == JsonWriteContext.STATUS_EXPECT_NAME) {
if (!_writeContext.writeValue()) {
_reportError("Can not "+typeMsg+", expecting field name");
}
if (_handleFirstLine) {
Expand Down Expand Up @@ -959,6 +1003,8 @@ protected void finishRow() throws IOException
{
_writer.endRow();
_nextColumnByName = -1;
_skipValue = false;
_basePath.setLength(0);
}

protected void _handleFirstLine() throws IOException
Expand Down
Loading