Repository navigation
Conversation
2b5190f to
68b4bb3
Compare
There was a problem hiding this comment.
🟡 Changes recommended
The current wildcard registration logic can overwrite explicitly-registered (JavaType, STRING) converters depending on registration order, and one new test reads the “workbook” converter map via currentWriteHolder() which may not actually be the workbook holder after a write.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR fixes a long-standing inconsistency where custom write converters registered with a wildcard excel-type key (supportExcelTypeKey() == null) were applied for XLSX writes but silently ignored for CSV writes (which force converter lookup under (JavaType, STRING)). The approach is to duplicate wildcard registrations under the CSV lookup key without changing the converter resolution logic.
Changes:
- Register wildcard-key custom converters under both
(JavaType, null)and(JavaType, STRING)during write-holder initialization. - Add a new regression test suite covering wildcard vs explicit converter behavior across XLSX/CSV and across workbook/sheet holder registration paths.
- Document the
supportExcelTypeKey() == nullwildcard semantics in both EN and zh-cn docs and in theConverterjavadoc.
File summaries
| File | Description |
|---|---|
fesod-sheet/src/main/java/org/apache/fesod/sheet/write/metadata/holder/AbstractWriteHolder.java |
Adds helper-based converter registration and duplicates wildcard registrations for CSV compatibility. |
fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/Converter.java |
Expands javadoc to document wildcard semantics and CSV behavior. |
fesod-sheet/src/test/java/org/apache/fesod/sheet/converter/WildcardConverterWriteTest.java |
Adds regression tests for wildcard converter application and registration in holder maps. |
website/docs/sheet/write/converter.md |
Documents wildcard-key behavior (EN). |
website/i18n/zh-cn/docusaurus-plugin-content-docs/current/sheet/write/converter.md |
Documents wildcard-key behavior (zh-cn). |
Review details
- Files reviewed: 5/5 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| private void putCustomConverter(Converter<?> converter) { | ||
| getConverterMap() | ||
| .put( | ||
| ConverterKeyBuild.buildKey(converter.supportJavaTypeKey(), converter.supportExcelTypeKey()), | ||
| converter); | ||
| if (converter.supportExcelTypeKey() == null) { | ||
| getConverterMap() | ||
| .put( | ||
| ConverterKeyBuild.buildKey(converter.supportJavaTypeKey(), CellDataTypeEnum.STRING), | ||
| converter); | ||
| } | ||
| } |
There was a problem hiding this comment.
Addressed: the (JavaType, STRING) registration now only replaces the occupant while it is still the built-in default converter (identity-checked against DefaultConverterLoader.loadDefaultWriteConverter()), so an explicitly registered converter is never displaced regardless of registration order; supportExcelTypeKey() is cached in a local. Regression test csvExplicitStringKeyRegisteredBeforeWildcardStillWins covers the order you described.
| Map<ConverterKeyBuild.ConverterKey, Converter<?>> workbookMap = | ||
| excelWriter.writeContext().currentWriteHolder().converterMap(); | ||
| Map<ConverterKeyBuild.ConverterKey, Converter<?>> sheetMap = | ||
| excelWriter.writeContext().writeSheetHolder().converterMap(); | ||
| excelWriter.finish(); |
There was a problem hiding this comment.
Addressed: workbookMap now reads writeWorkbookHolder().converterMap() — after write() the current holder is the sheet holder, so the two maps were aliased. The writer is now closed via try-with-resources.
68b4bb3 to
9f2782d
Compare
BigDataDZ
left a comment
There was a problem hiding this comment.
Reviewed as the author of the read-side counterpart (#1086) - this is the right shape for the write side, and I can confirm the registration semantics here are consistent with what the read-side fix ended up doing.
What stands out as particularly well done:
- The
putCustomConverterguard is the part I was worried about before reading it: expanding a wildcard(JavaType, null)registration into(JavaType, STRING)would naively displace an explicitly registered(JavaType, STRING)converter depending on registration order. Taking over the slot only when the occupant is absent or identity-equal to the built-in default (== DefaultConverterLoader.loadDefaultWriteConverter().get(stringKey)) makes the outcome order-independent, and the shallow parent copy keeps the identity check valid for inherited holders too. - The wildcard's
(JavaType, STRING)slot is also found by the xlsx path when a field's target cell type is explicitly STRING, so behavior is consistent for both formats regardless of which key the executor builds - a bonus beyond the CSV fix itself. - The
Converterjavadoc + bilingual docs make the null contract explicit, which was my main gripe as a user hitting this in #1056's repro.
Two non-blocking thoughts:
- Once this and #1086 both land, the "Converter Resolution Priority" docs section could use a short paragraph describing the read/write asymmetry: read resolves by the concrete cell type key (xlsx/xls included), write resolves wildcard on xlsx/xls but forced STRING on CSV. Happy to draft that paragraph when either merges, so the two sides are documented together rather than in two places.
- The class-level javadoc on
XlsFormatTrackingListener-style details aside,WildcardConverterWriteTestcovering explicit-priority preservation (explicit(JavaType, STRING)registered before AND after a wildcard registration) would lock the order-independence claim - if it is already in there, even better.
Nice work - this unblocks the converter story for CSV writes that #1045/#1056 have been waiting on.
|
Thanks for the careful read — and for checking the guard against the shallow parent copy specifically. Order-independence was the exact thing Copilot's earlier review flagged, so it's good to have it confirmed from the read-side perspective as well. On your two points:
Also merged current main into the branch, so it's no longer behind. |
|
Great - here is a draft for the shared paragraph, written to drop into the "Converter Resolution Priority" section of both docs pages (read and write). Wording follows what our two implementations actually do: Converter resolution across read and writeRegistered converters are looked up by the key Write. The target cell type is Read. Cells carry a concrete type from the file, so every format resolves In short: one wildcard registration covers every format and every cell type, an explicit registration always beats the wildcard, and user registrations always beat built-ins. If the wording works for you, I will add it to the read-side converter docs in #1086 (both EN and zh-CN, mirroring what you did for the write page); if #1069 merges first, feel free to fold the same text into the write page so neither ships a half story. Edits welcome, it is meant as a starting point. |
|
Thanks — this reads well, and I went through it against both implementations line by line. The write-side description matches #1069 exactly (including why the extra One addition worth considering: with the header-path fix in #1098, the header row resolves against the built-in converters only, so "user registrations always beat built-ins" could be read as universal. A closing sentence would keep the summary honest, e.g. "The header row is resolved against the built-in converters only: registered converters apply to data cells, never to the header text used for On placement: agreed on one story rather than two. Go ahead and add it to the read page in #1086. I'll fold the same paragraph (plus that header note once #1098 is in) into the write page after either PR lands, so no page documents behaviour that has not shipped yet — but if you'd rather cover both pages in #1086, that works for me too. |
Add the shared "Converter resolution across read and write" paragraph to the read-side converter docs (EN and zh-CN), as agreed in apache#1069: how the (JavaType, CellDataType) lookup key is built per direction, why explicit registrations beat wildcard ones, and how CSV's forced STRING write type interacts with the wildcard registration. The header-row note is intentionally left out until apache#1098 lands. Generated-by: ZCode
|
Done — added the shared paragraph to the read page in #1086 (EN and zh-CN), under a "Converter Resolution Priority" section so it drops into the write page's same-named section unchanged. Commit: e717b5d. On the header note: agreed it should wait until #1098 lands, so I left it out — the read page now only describes shipped behaviour. Over to you for the write-page fold-in once either PR merges. |
What changed
Custom converters registered with
supportExcelTypeKey() == null(the wildcard key(JavaType, null)) are now also registered under(JavaType, STRING). A singleregisterConverter(...)call now works for both xlsx and CSV writes.Why
AbstractExcelWriteExecutor#doConvertresolves converters with the key(JavaType, targetCellDataType). The xlsx write path leaves the target typenull, so a wildcard registration(JavaType, null)is found. The CSV write path forces the target type toSTRING(csv is converted to string by default), so the same registration misses and the built-in string converter silently takes over: no warning is logged and the CSV output silently diverges from the xlsx output of the same code. See #1045 and #1056 (same root cause as EasyExcel#3054).How
AbstractWriteHolderregisters custom converters at two sites (own registration plus replay of workbook-level registrations when a sheet holder is created); both now delegate to a singleputCustomConverterhelper. When the declared excel type key isnull, the converter is additionally registered under(JavaType, STRING)— the key the CSV lookup uses. No lookup code changes; the write path is untouched.Impact
null-key) registrations: CSV now applies the custom converter instead of silently falling back to the built-in string converter — matching the declared wildcard intent (matches all cell data types). The template-fill write path shares the same registry and benefits automatically.@ExcelProperty(converter = ...)) bypass the registry entirely and are unaffected.Documentation
The
nullsemantics ofConverter#supportExcelTypeKey()are now documented on the method (javadoc) and on the website converter page (EN and zh-cn).Tests
New
WildcardConverterWriteTest(7 tests):true/falseinstead of the custom valuestrue/false)STRING-key registration unaffectedSTRING-key registration before the wildcard still wins (registration-order guard)STRING-key registration after the wildcard still wins (registration-order guard)Red/green verified: the failing cases were reproduced against the pre-fix code, then turned green by the fix.
Full suite: 1051/1051 green; spotless and javadoc pass.
Fixes #1045
Fixes #1056