feat: honor wildcard custom converters when reading - #1086
Conversation
Signed-off-by: BigDataDZ <76271875+BigDataDZ@users.noreply.github.com>
18c5070 to
8f0ab7a
Compare
skytin1004
left a comment
There was a problem hiding this comment.
Hi @BigDataDZ,
I tried registering a STRING converter first and a wildcard converter second. Before this change, the STRING converter handled string cells. With this change, the wildcard converter handles them instead.
Is this change intentional? If not, could you keep the STRING converter handling these cells and add a test for this registration order?
Signed-off-by: BigDataDZ <76271875+BigDataDZ@users.noreply.github.com>
|
Good catch — that behavior change was not intentional. Fixed in d318eea: Custom registrations now happen in two phases — first every converter is registered at its exact Added the suggested registration-order test ( |
There was a problem hiding this comment.
@BigDataDZ Thanks for addressing this. I verified that the explicit converter keeps precedence over a later wildcard registration, while the wildcard case still works. LGTM.
Mikkey-f
left a comment
There was a problem hiding this comment.
I went through the read-side lookup path to compare it with the write-side change in #1069, and the mechanism looks right. A few notes below, one of which I think is worth covering before merge.
Verified
- No leak risk:
AbstractReadHolderbuilds a per-instanceHashMap, and the expansion only writes to that copy — the shared default map is untouched. - The
explicitKeysfirst pass makes the "explicit wins regardless of registration order" guarantee hold (ConverterKeyimplements equals/hashCode, socontains()is reliable). - Skipping
EMPTYis safe: both lookup sites (convertToStringMap,doConvertToJavaObject) short-circuit EMPTY before reaching the map. - In the expanded set,
ERRORis genuinely reachable (in v03,FormulaRecordHandlerleaves error cells as ERROR; only later formats normalise them to STRING), so the expansion is not dead code there.RICH_TEXT_STRINGdoes not appear to be produced on the read side — harmless, just unused.
Why this differs from #1069 (write side)
Write lookups key on (javaType, targetCellDataType), where xlsx targets are null (so the wildcard key matches directly) and only the CSV / fill paths force a concrete type — hence STRING is enough there. Read lookups key on the actual cell type, so honouring a wildcard means expanding to every concrete type. A one-line comment near the expansion loop would help keep this asymmetry from looking like a bug to the next reader.
The case I would suggest addressing: the expansion also reaches the header path
ConverterUtils.convertToStringMap (used by DefaultAnalysisEventProcessor.buildHead for header-to-field matching and by AnalysisEventListener.invokeHeadMap) looks up (String.class, cellDataType). Because the expansion writes (String, STRING) and friends for a wildcard Converter<String>, the converter now runs on header cells as well.
Local check — bean with @ExcelProperty("flag"), file with header flag and cell abc, converter that upper-cases a wildcard-registered String:
- on
main: the converter is never invoked and the value reads back asabc(today's no-op); - with this change: it is invoked with "flag" (the header), the header text becomes "FLAG", name matching finds no column, and the field comes back null with no exception. With
@ExcelProperty(index = 0)it still works, since matching then ignores the header text.
So for a wildcard String converter this turns a no-op into silently empty fields. The tests here do not catch it because they register a Boolean converter while the header lookup is for String.class.
To be clear, this is not introduced by your change — an explicit (String, STRING) registration already behaves this way on main (I checked: same silent null). But the PR widens the set of registrations that hit it, and a generic string converter is exactly the kind of thing people register wildcard-style.
I filed #1098 with the repro and root cause, and I am happy to take that fix so this PR can stay focused on the wildcard expansion — my suggestion there is that the header path should resolve against the built-in converters rather than the user-extended map. If you would rather handle it here, just say so and I will close the issue and follow your lead; either way, a test with a wildcard String converter would be worth adding on your side.
Worth knowing: until that header fix lands, a wildcard Converter<String> would give silently empty fields for name-based matching, so if you or the maintainers prefer to land the header fix first, I will move quickly on it.
Signed-off-by: BigDataDZ <76271875+BigDataDZ@users.noreply.github.com>
|
Thanks for the incredibly thorough review @Mikkey-f — the leak/ordering/EMPTY/ERROR walk-through
One sequencing note for maintainers: until #1098 lands, a wildcard |
Purpose of the pull request
Custom converters registered with
supportExcelTypeKey() == null(the wildcard contract: "matchesevery cell data type") never applied on the read path: the built-in concrete-key converter silently
took over, or reading failed with
Converter not found. With this change, a singleregisterConverter(...)call works for reading exactly as it already does for writing.Closed: #1085
What's changed?
AbstractReadHoldernow routes custom converter registration throughregisterCustomConverter,which — when
supportExcelTypeKey()isnull— additionally registers the converter under everyconcrete
CellDataTypeEnumkey (all exceptEMPTY, which is filtered before lookup).Read lookups (
ConverterUtils#convertToJavaObject,ConverterUtils#convertToStringMap) use theconcrete cell type as key, so a wildcard registration at
(JavaType, null)never matched: e.g. awildcard
Booleanconverter reading a"yes"cell producedfalsevia the built-inBooleanStringConverter(Boolean.valueOf("yes")), with no warning. This is the read-path twin of#1045/#1056 (write/CSV flavor, addressed by PR #1069) and mirrors that PR's registration-expansion
approach, expanded to all concrete types because read lookups can arrive with any cell type.
Lookup code is untouched. Explicit-key registrations keep their priority semantics, and users
without wildcard custom converters are unaffected.
New
WildcardConverterReadTest(round-trip): the wildcard-read case fails on currentmain(
"yes"read asfalse) and passes with this change; the explicitSTRING-key case is aregression guard. Full fesod-sheet suite: 920/920 green.
Checklist