Skip to content

[bug]Custom converters with supportExcelTypeKey() == null are silently ignored when writing CSV #1056

Description

@luo-zhan

Search before asking

  • I searched in the issues and found nothing similar.
    (EasyExcel#3054 reported the same root cause, but no corresponding issue exists in this repo after the Fesod migration.)

Fesod version

2.0.1-incubating (the code path is unchanged on current main)

JDK version

1.8.0_482

Operating system

macOS 15.2

Steps To Reproduce

Register a custom converter whose supportExcelTypeKey() returns null (the documented way to match all Excel cell types), then write the same data as xlsx and as CSV:

static class BooleanYesNoConverter implements Converter<Boolean> { 
   @Override 
   public Class<?> supportJavaTypeKey() {
       return Boolean.class; 
   }
   @Override 
   public CellDataTypeEnum supportExcelTypeKey() {
        // null = match all Excel cell data types
        return null;
   }

  @Override 
  public WriteCellData<?> convertToExcelData(WriteConverterContext<Boolean> context) {
        return new WriteCellData<>(Boolean.TRUE.equals(context.getValue()) ? "YES" : "NO");
  }
}

// 1) xlsx: the custom converter works
FesodSheet.write(xlsxFile)
.excelType(ExcelTypeEnum.XLSX)
.registerConverter(new BooleanYesNoConverter())
.sheet()
.doWrite(Collections.singletonList(Collections.singletonList(Boolean.TRUE)));

// 2) csv: the same converter is silently ignored
FesodSheet.write(csvFile)
.excelType(ExcelTypeEnum.CSV)
.charset(StandardCharsets.UTF_8)
.registerConverter(new BooleanYesNoConverter())
.sheet()
.doWrite(Collections.singletonList(Collections.singletonList(Boolean.TRUE)));

Current Behavior

  • xlsx output: YES (custom converter applied)
  • CSV output: true — the custom converter is silently ignored, the built-in BooleanStringConverter takes over. No warning is logged.

Expected Behavior

The custom converter should apply for CSV as well: output YES.

Root cause

AbstractExcelWriteExecutor#doConvert forces the lookup key to (JavaType, STRING) in CSV mode:

 if (converter == null) { // csv is converted to string by default 
    if (writeContext.writeWorkbookHolder().getExcelType() == ExcelTypeEnum.CSV) {          
           cellWriteHandlerContext.setTargetCellDataType(CellDataTypeEnum.STRING);
     } 
    converter = writeContext.currentWriteHolder().converterMap() 
             .get(ConverterKeyBuild.buildKey(originalFieldClass, targetCellDataType)); 
}
  • xlsx: lookup key is (Boolean, null) → hits the custom converter registered under (Boolean, null)
  • csv: lookup key is forced to (Boolean, STRING) → the custom converter misses, the built-in BooleanStringConverter wins ✗

Note the trap is two-sided: changing supportExcelTypeKey() to STRING fixes CSV but silently breaks xlsx (the lookup then misses for xlsx and the built-in BooleanBooleanConverter writes a BOOLEAN cell). A single static registration key cannot match both formats.

EasyExcel#3054 reported the same root cause ("custom converter is not applied when writing CSV"). Since Fesod inherits this code path, the problem carries over.

Suggested fixes (any one would work)

  1. In CSV mode, fall back to the (JavaType, null) lookup when (JavaType, STRING) misses (best backward compatibility);
  2. Or register null-key converters under both (JavaType, null) and (JavaType, STRING) at registration time;
  3. Or at minimum log a warning when a registered custom converter is shadowed in CSV mode.

Workaround

For anyone hitting this: wrap the converter for CSV writes only and force its registration key to STRING, delegating the conversion logic:

static class CsvStringKeyAdapter<T> implements Converter<T> { 
    private final Converter<T> delegate; 
    CsvStringKeyAdapter(Converter<T> delegate) {
         this.delegate = delegate; 
    }
    @Override
    public Class<?> supportJavaTypeKey() { return delegate.supportJavaTypeKey(); }

    @Override 
    public CellDataTypeEnum supportExcelTypeKey() { return CellDataTypeEnum.STRING; }

    @Override public WriteCellData<?> convertToExcelData(WriteConverterContext<T> context) throws Exception {
          return delegate.convertToExcelData(context);
    }
}
// CSV write: builder.registerConverter(new CsvStringKeyAdapter<>(myConverter)); 
// xlsx write: builder.registerConverter(myConverter);

Are you willing to submit a PR?

  • I'm willing to submit a PR!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions