This is an automated email from the ASF dual-hosted git repository.
jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git
The following commit(s) were added to refs/heads/master by this push:
new affabe50f3 fix: coerce typed map keys in BeanMap.put
affabe50f3 is described below
commit affabe50f3e5228dfe8a6bf09d886feae2cb848e
Author: James Bognar <[email protected]>
AuthorDate: Fri May 22 11:16:12 2026 -0400
fix: coerce typed map keys in BeanMap.put
---
.../juneau/commons/bean/BeanPropertyMeta.java | 16 +-
.../test/java/org/apache/juneau/BeanMap_Test.java | 38 ++++
...INISHED-14-beanpropertymeta-map-key-coercion.md | 79 +++++++++
todo/TODO-14-beanpropertymeta-map-key-coercion.md | 191 ---------------------
todo/TODO.md | 2 -
5 files changed, 128 insertions(+), 198 deletions(-)
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanPropertyMeta.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanPropertyMeta.java
index ad04ae0f25..2b574cdd2b 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanPropertyMeta.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanPropertyMeta.java
@@ -1140,6 +1140,7 @@ public class BeanPropertyMeta implements
Comparable<BeanPropertyMeta> {
var valueMap = (Map)value1;
var propMap = (Map)r;
+ var keyType = rawTypeMeta.getKeyType();
var valueType = rawTypeMeta.getValueType();
// If the property type is abstract, then we
either need to reuse the existing
@@ -1152,9 +1153,11 @@ public class BeanPropertyMeta implements
Comparable<BeanPropertyMeta> {
propertyClass.getName(), cn(value1));
if
(propertyClass.isInstance(valueMap)) {
- if (!
valueType.isObject()) {
+ if (!
(keyType.isObject() && valueType.isObject())) {
var
needsConversion = Flag.create();
valueMap.forEach((k, v2) -> {
+ if
(nn(k) && ! keyType.isObject() && ! keyType.isInstance(k))
+
needsConversion.set();
if
(nn(v2) && ! valueType.isInstance(v2)) {
needsConversion.set();
}
@@ -1178,12 +1181,15 @@ public class BeanPropertyMeta implements
Comparable<BeanPropertyMeta> {
}
// Set the values.
- var propMap2 = propMap;
- valueMap.forEach((k1, v1) -> {
+ for (var e :
((Set<Map.Entry>)valueMap.entrySet())) {
+ var k1 = e.getKey();
+ var v1 = e.getValue();
+ if (! keyType.isObject())
+ k1 = session.convertToType(k1,
keyType);
if (! valueType.isObject())
v1 = session.convertToType(v1,
valueType);
- propMap2.put(k1, v1);
- });
+ propMap.put(k1, v1);
+ }
if (nn(setter) || nn(field))
invokeSetter(bean, pName, propMap);
diff --git a/juneau-utest/src/test/java/org/apache/juneau/BeanMap_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/BeanMap_Test.java
index 0a69fcb4ed..f17395b660 100755
--- a/juneau-utest/src/test/java/org/apache/juneau/BeanMap_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/BeanMap_Test.java
@@ -1794,6 +1794,44 @@ class BeanMap_Test extends TestBase {
public void setA(List<String> v) { a = v; }
}
+
//====================================================================================================
+ // Typed map keys in BeanMap.put should coerce to property key type.
+
//====================================================================================================
+ @Test void a41_typedMapField_coercesStringKeysToEnum() {
+ var a = MarshallingContext.DEFAULT.toBeanMap(new AB());
+ a.put("m", map("ONE","v1","TWO","v2"));
+
+ var b = a.getBean();
+ assertEquals("v1", b.m.get(HEnum.ONE));
+ assertEquals("v2", b.m.get(HEnum.TWO));
+ assertFalse(((Map)b.m).containsKey("ONE"));
+ assertTrue(b.m.keySet().stream().allMatch(x -> x instanceof
HEnum));
+ }
+
+ public static class AB {
+ public Map<HEnum,String> m;
+ }
+
+ @Test void a42_typedMapSetter_coercesStringKeysToEnum() {
+ var a = MarshallingContext.DEFAULT.toBeanMap(new AC());
+ a.put("m", map("ONE","v1","TWO","v2"));
+
+ var b = a.getBean();
+ assertEquals("v1", b.getM().get(HEnum.ONE));
+ assertEquals("v2", b.getM().get(HEnum.TWO));
+ assertFalse(((Map)b.getM()).containsKey("ONE"));
+ assertTrue(b.getM().keySet().stream().allMatch(x -> x
instanceof HEnum));
+ }
+
+ public static class AC {
+ private final HashMap<HEnum,String> m = new HashMap<>();
+ public HashMap<HEnum,String> getM() { return m; }
+ public void setM(HashMap<HEnum,String> v) {
+ m.clear();
+ m.putAll(v);
+ }
+ }
+
//====================================================================================================
// containsKey with plain beans vs @MarshalledProp(name="*")
dyna/extras map
//====================================================================================================
diff --git a/todo/FINISHED-14-beanpropertymeta-map-key-coercion.md
b/todo/FINISHED-14-beanpropertymeta-map-key-coercion.md
new file mode 100644
index 0000000000..c97b8a4ec9
--- /dev/null
+++ b/todo/FINISHED-14-beanpropertymeta-map-key-coercion.md
@@ -0,0 +1,79 @@
+# FINISHED-14 — Close the latent map-key coercion gap in
`BeanPropertyMeta.setPropertyValue`
+
+Completed: 2026-05-22
+
+## Outcome
+
+TODO-14 is complete. `BeanPropertyMeta.setPropertyValue(...)` now performs
symmetric key/value type checks and coercion for typed `Map<K,V>` properties,
so `BeanMap.put(...)` no longer leaves wire-form keys (for example `String`) in
typed-key maps (for example `Map<Enum, String>`).
+
+## Implemented changes
+
+### 1) Commons-side production fix
+
+File:
+-
`juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanPropertyMeta.java`
+
+Changes:
+- In the typed-map branch of `setPropertyValue(...)`, pulled `keyType` from
`rawTypeMeta.getKeyType()` alongside `valueType`.
+- Extended `needsConversion` detection to include key-side mismatches:
+ - `nn(k) && ! keyType.isObject() && ! keyType.isInstance(k)`
+- Kept value-side mismatch detection in place.
+- Replaced the map `forEach` write loop with an explicit `for (Map.Entry...)`
loop so both key and value can be reassigned after conversion.
+- Added key-side conversion before insertion:
+ - `if (! keyType.isObject()) k1 = session.convertToType(k1, keyType);`
+- Retained value-side conversion behavior.
+
+Result:
+- Both abstract-map and concrete-map writable paths now coerce keys to the
declared key type.
+
+### 2) Regression/coverage test additions
+
+File:
+- `juneau-utest/src/test/java/org/apache/juneau/BeanMap_Test.java`
+
+Added tests:
+- `a41_typedMapField_coercesStringKeysToEnum`
+ - Exercises `BeanMap.put("m", Map<String,String>)` into `Map<HEnum,String>`
field property.
+- `a42_typedMapSetter_coercesStringKeysToEnum`
+ - Exercises setter-backed `HashMap<HEnum,String>` property.
+
+Assertions verify:
+- Enum-key lookups succeed.
+- Raw `"ONE"` key is absent after coercion.
+- All resulting keys are `HEnum` instances.
+
+## Verification run
+
+### Focused area
+- `mvn -pl juneau-utest -am -Dtest=BeanMap_Test
-Dsurefire.failIfNoSpecifiedTests=false test`
+ - `org.apache.juneau.BeanMap_Test`: **46 run, 0 failures, 0 errors**
+
+### Parser regression suite (Bug #7b parser families)
+- `mvn -pl juneau-utest -am
-Dtest='Hjson*Test,Hocon*Test,Proto*Test,Bson*Test'
-Dsurefire.failIfNoSpecifiedTests=false test`
+ - Hjson/Hocon/Proto/Bson suites remained green (no failures/errors).
+
+### Enum matrix acceptance check
+- `mvn -pl juneau-utest -am -Dtest=EnumFormat_RoundTrip_Test
-Dsurefire.failIfNoSpecifiedTests=false test`
+ - `EnumFormat_RoundTrip_Test`: **2268 run, 0 failures, 0 errors, 0 skipped**
+
+### Broader suite
+- `./scripts/test.py`
+ - Build phase: success
+ - Test phase: success
+
+## Phase 4 audit summary (sibling-shape pass)
+
+Reviewed:
+- `BeanPropertyMeta.set(...)` dispatch path
+- `BeanPropertyMeta.setPropertyValue(...)` map/collection branches
+- dyna-property path (`isDyna`) in setter/getter dispatch
+
+Findings:
+- No additional `Map<K,V>` key-side mismatch/coercion gaps remain in this
method.
+- `Collection` branch handles only element type (`E`) and has no key-side
analog.
+- Public `set(...)` path routes through the same corrected
`setPropertyValue(...)` map logic.
+- Dyna property writes are name-based map/bean extras routing and do not
represent typed `Map<K,V>` key coercion semantics.
+
+Disposition:
+- No new TODO was required from this audit pass for TODO-14 scope.
+- Existing separate work on sibling shapes (for example typed set element
coercion) remains tracked independently (see TODO-58).
diff --git a/todo/TODO-14-beanpropertymeta-map-key-coercion.md
b/todo/TODO-14-beanpropertymeta-map-key-coercion.md
deleted file mode 100644
index f3cfd6e052..0000000000
--- a/todo/TODO-14-beanpropertymeta-map-key-coercion.md
+++ /dev/null
@@ -1,191 +0,0 @@
-# TODO-14 — Close the latent map-key coercion gap in
`BeanPropertyMeta.setPropertyValue`
-
-Source: promoted from **Open Question 6** of
`todo/TODO-57-format-round-trip-tests.md` after the Bug #7b per-parser fixes
(Hjson / Hocon / Proto / Bson) rendered the underlying commons-side gap
unreachable from any tested parser. Filed 2026-05-22 to track defense-in-depth
closure of the commons-side site so a fifth parser surfacing with the same
shape doesn't need a fifth per-parser fix.
-
----
-
-## 1. Background / context
-
-### The exact site
-
-`juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanPropertyMeta.java`
— inside the private `setPropertyValue(BeanMap, String, Object, Object,
boolean, boolean, BeanSession)` method (declared at line 1119), the
typed-`Map<K, V>` branch:
-
-- **`needsConversion` predicate (lines 1156–1163).** A `Flag` is set if *any*
entry value `v2` fails `valueType.isInstance(v2)`; the predicate is **never**
consulted against entry keys. If only the key needs coercion, the flag stays
clear and `convertToType` is not called.
-
-
```1156:1163:juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanPropertyMeta.java
- var
needsConversion = Flag.create();
-
valueMap.forEach((k, v2) -> {
- if
(nn(v2) && ! valueType.isInstance(v2)) {
-
needsConversion.set();
- }
- });
- if
(needsConversion.isSet())
-
valueMap = (Map)session.convertToType(valueMap, rawTypeMeta);
- ```
-
-- **Value-only coercion loop (lines 1180–1188).** The second `forEach` that
handles the writable-property path converts each `v1` against `valueType` but
never inspects `k1` against the property's key type. The key is forwarded to
`propMap2.put(k1, v1)` as-is.
-
-
```1180:1188:juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanPropertyMeta.java
- // Set the values.
- var propMap2 = propMap;
- valueMap.forEach((k1, v1) -> {
- if (! valueType.isObject())
- v1 = session.convertToType(v1,
valueType);
- propMap2.put(k1, v1);
- });
- if (nn(setter) || nn(field))
- invokeSetter(bean, pName, propMap);
- ```
-
-Both half-decisions need to inspect the entry key against
`rawTypeMeta.getKeyType()` (already available — `ClassMeta` exposes a public
`getKeyType()` at line 642 of `ClassMeta.java`) and trigger the symmetric
coercion path when the key type doesn't match.
-
-### Why the gap exists
-
-The `needsConversion` predicate was written when bean properties were
predominantly `Map<String, V>`, where the key is always a `String` produced by
the parser tokenizer and never needs coercion. When typed-`K` map properties
(e.g. `Map<TestEnum, String>`) were added, the value-side branch was extended
to thread the parameterized `V` type through `valueType.isInstance(v2)`, but
the matching key-side branch was never added. The result: a parser that hands a
fully-built `Map<String, V>` to [...]
-
-### Why it's currently unreachable
-
-Each of the four parsers that exhibited the symptom in the round-trip matrix
was fixed at the parser-specific dispatch site for Bug #7b in TODO-57:
-
--
`juneau-core/juneau-marshall/src/main/java/org/apache/juneau/hjson/HjsonParserSession.java`
— `propertyType` widened to surface the typed `ClassMeta<K>` so key coercion
happens at the parser's key-conversion site (`convertAttrToType` analog).
--
`juneau-core/juneau-marshall/src/main/java/org/apache/juneau/hocon/HoconParserSession.java`
— `hoconToMap` now threads the key type through to its own key-coercion step.
--
`juneau-core/juneau-marshall/src/main/java/org/apache/juneau/proto/ProtoParserSession.java`
— `convertValue` / `convertMapToType` propagate the key type to the per-key
coercion call.
--
`juneau-core/juneau-marshall/src/main/java/org/apache/juneau/bson/BsonParserSession.java`
— `parseDocument`'s `convertAttrToType` consumes the typed key class.
-
-For comparison, the JSON family already gets this right because
`JsonParserSession.parseIntoMap2` (line 559 of `JsonParserSession.java`)
accepts `ClassMeta<K> keyType` as a method parameter and calls
`convertAttrToType(m, currAttr, keyType)` at line 598 *before* the entry ever
lands in the bean property. The map handed to `setPropertyValue` already has
`TestEnum` keys, so the commons-side gap is never hit.
-
-Net: every existing parser either (a) handles key coercion before the map
reaches `setPropertyValue` (JSON family + the four parsers fixed for Bug #7b)
or (b) doesn't need to because the property type is `Map<String, V>` (the
common case).
-
-### The systemic risk
-
-The gap is latent, not dead. A future parser implementation — or a regression
in any of the five parsers above — that produces a `Map<String, V>` for a
typed-`Map<K, V>` property would reproduce the symptom and require *another*
per-parser fix. The fix-here-or-fix-the-commons-side decision was deliberately
deferred during the Bug #7b closure on the grounds that:
-
-- The four parser sites were simpler than the commons-side change (each parser
already had a per-entry key conversion site, so the edit was localized).
-- A commons-side change risks regressing the JSON family because the JSON
family's parser-level conversion already runs; a commons-side coercion that
fires on a key that's already the correct type is a no-op but still adds a
`convertToType` call to a hot path.
-- The matrix wasn't surfacing it from the JSON family anyway.
-
-This plan revisits that deferral as a defense-in-depth follow-on, not as a
correctness fix.
-
----
-
-## 2. Scope
-
-### In scope
-
-- Single fix site: the typed-`Map<K, V>` branch of `setPropertyValue` in
`juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanPropertyMeta.java`.
-- Extend the `needsConversion` predicate (lines 1156–1163) to also flag when
any non-null entry key fails `keyType.isInstance(k)` (with `keyType =
rawTypeMeta.getKeyType()`).
-- Add a symmetric key-side `convertToType` call in the value-loop at lines
1180–1186 — `if (! keyType.isObject()) k1 = session.convertToType(k1,
keyType);` — mirroring the existing value-side pattern.
-- A new unit test in `juneau-commons` that reproduces the gap *without going
through any parser* (see Phase 1).
-- A brief audit pass for sibling-shape gaps elsewhere in `BeanPropertyMeta`
(see Phase 4).
-
-### Out of scope
-
-- The four per-parser Bug #7b fixes — they stay in place. The commons-side fix
is a backstop, not a replacement.
-- Other latent commons-side type-coercion gaps (those go in their own
follow-up plans).
-- Generic-arity changes to `BeanPropertyMeta` — fix is limited to the `Map<K,
V>` key inspection.
-- Sibling shapes like `Set<E>` / `Iterable<E>` / generic-typed arrays — those
are flagged in Open Questions but not part of this plan's initial fix.
-
----
-
-## 3. Phases
-
-### Phase 1 — Reproduce the symptom in a commons-side unit test
-
-Land a new unit test under `juneau-utest` (or `juneau-commons`'s own test
source if a closer test home exists) that:
-
-1. Defines a small bean with a `Map<TestEnum, String>`-typed property (with
setter or public field — both shapes; the writable-property path and the
abstract-property path go through different branches of `setPropertyValue`).
-2. Builds a `BeanMap` for the bean from a `BeanSession`.
-3. Calls `BeanMap.put(propertyName, Map.of("FOO", "v1", "BAR", "v2"))` — i.e.
hands a `Map<String, String>` directly to the bean property, bypassing every
parser.
-4. Asserts that the bean's `Map<TestEnum, String>` getter returns a map whose
`keySet()` contains `TestEnum` instances, not `String`s.
-
-The test must **fail** on the current commons-side code (proving the gap is
real) and **pass** after the Phase 2 fix. Use a `TestEnum` defined in the
test's own scope to avoid coupling the unit test to a real format swap.
-
-Place the test next to existing `BeanPropertyMeta` coverage — search
`juneau-utest/src/test/java/org/apache/juneau/commons/bean/` (or the equivalent
commons-tests root) for `BeanPropertyMeta_*Test` files to identify the
conventional location.
-
-### Phase 2 — Implement the commons-side fix
-
-Edit `BeanPropertyMeta.setPropertyValue` to:
-
-1. **Extend the `needsConversion` predicate (lines 1156–1163).** Pull
`rawTypeMeta.getKeyType()` into a local `keyType`. The `forEach` body extends
to also set the flag when `nn(k) && ! keyType.isInstance(k)`. Skip the key
check if `keyType.isObject()` (the wildcard / abstract-key case — same shape as
the existing `! valueType.isObject()` guard).
-2. **Extend the value-loop coercion (lines 1180–1186).** Inside the second
`forEach`, mirror the value-side coercion for the key: `if (!
keyType.isObject()) k1 = session.convertToType(k1, keyType);`. Then call
`propMap2.put(k1, v1)` with the coerced key. (`forEach` lambdas can't reassign
captured locals — this likely needs to switch to an explicit `for (var e :
valueMap.entrySet())` iteration, the same shape used in the
`Collection`-branch's `ListIterator` block at lines 1213–1221.)
-
-Both changes are surgical and the existing `convertToType` plumbing is already
wired through `session`. No new `ClassMeta` API, no `BeanSession` change, no
signature change on `setPropertyValue`.
-
-### Phase 3 — Regression check against the four Bug-#7b parsers
-
-Run the four parsers that received per-parser Bug #7b fixes:
-
-```bash
-mvn -pl juneau-utest -am -Dtest='Hjson*Test,Hocon*Test,Proto*Test,Bson*Test'
test
-```
-
-Expected outcome: green. The per-parser fixes still fire at the parser level
(they convert keys before the map reaches `setPropertyValue`), so the
commons-side fix is a no-op for those parsers — `keyType.isInstance(k)` returns
true, the flag stays clear, the second forEach skips the key-side
`convertToType`.
-
-Optional follow-on (defer unless verified clean): consider whether the
per-parser fixes can be *simplified* now that the commons-side gap is closed.
Default disposition is to **leave them in place** since:
-
-- They're working, well-tested, and have already shipped through the
round-trip matrix.
-- Removing them shifts the conversion cost from parse-time (early) to
bean-property-assignment-time (later) — which is the symmetric cost question
discussed in Open Question 1 below.
-- A regression in any per-parser fix would now manifest as a silent
commons-side coercion rather than a clean parser-level test failure.
-
-Only revisit the per-parser fixes if a measurable performance win is
demonstrated by Open Question 1's microbenchmark.
-
-### Phase 4 — Sibling-shape audit pass
-
-Read through `BeanPropertyMeta.setPropertyValue` and `BeanPropertyMeta.set`
(the public entry point) looking for other places where the code inspects
entry-value types but not entry-key types — or, more broadly, where a
parameterized type is consulted in one direction but not the other. Candidates
to look at explicitly:
-
-- The `Collection` branch (lines 1190–1247) — element type is inspected; not
parameterized (no key analog), so likely fine.
-- The `BeanPropertyMeta.set` overload that takes a `Object` directly (above
line 1119) — confirm it dispatches through the same `setPropertyValue` site and
doesn't have its own parallel logic.
-- The dyna-property branch (`isDyna` near line 1097) — confirm it routes
through the same conversion machinery or document why it doesn't.
-
-Output of the audit pass: either (a) "no additional sibling gaps found"
recorded in this plan, or (b) a list of follow-up TODO items for any sibling
shapes that need their own plans.
-
----
-
-## 4. Open questions
-
-1. **Performance impact.** The existing per-parser Bug #7b fixes coerce keys
at parse time using each parser's existing `convertAttrToType` (or analog),
which is a tight, format-specific path. The commons-side fix coerces keys
*after* the parser has already produced a `Map<String, V>` — adds a second
iteration over the map's entries. Worth measuring with a JMH microbenchmark on
a typed `Map<Enum, V>` bean property to confirm the impact is negligible.
Hypothesis: for maps with `< ~100` en [...]
-2. **Backwards compatibility.** The commons-side fix changes the *observable
semantics* of `BeanPropertyMeta.setPropertyValue` for any caller that was
passing a wire-form-typed `Map` (e.g. a custom parser implementation) and
*intentionally* leaving keys as `String`s because the downstream consumer was
prepared for that shape. We don't know of any such caller, but the behavior
change is potentially observable. Worth considering: (a) a deprecation cycle
with a `MarshalledConfig` opt-in fla [...]
-3. **Scope of `needsConversion`.** Should the same inspection extend to:
- - `Set<E>` element types? (probably yes — same shape, single parameterized
type per collection)
- - `Iterable<E>` element types? (probably yes — but only if the bean
property declares a typed `Iterable`, which is rare)
- - Generic-typed arrays (`T[]` where `T` is bound to a type variable)?
(probably yes — but uncovered by any existing test)
- The plan flags these as Phase 4 audit candidates. The initial fix is
intentionally narrow to `Map<K, V>` keys; sibling shapes are tracked separately
to keep this plan reviewable.
-4. **`keyType.isObject()` vs. raw-type detection.** The value-side guard is `!
valueType.isObject()`. For map keys, the analogous wildcard case is `Map<?, V>`
or a raw `Map`. Need to confirm that `rawTypeMeta.getKeyType()` returns a
`ClassMeta` whose `isObject()` is true in both wildcard and raw-map cases (and
not, say, `null`). Read `ClassMeta.getKeyType()` (line 642 of `ClassMeta.java`)
and trace its behavior for a raw `Map` declaration before relying on the same
guard shape.
-5. **Lambda vs. for-loop refactor.** Phase 2 notes that the second `forEach`
likely needs to switch to an explicit `for (var e : valueMap.entrySet())` to
allow reassigning `k1`. Worth checking whether the existing `forEach` was
load-bearing (e.g. for a specific `Map` implementation's iteration semantics)
before refactoring. Default disposition: switch to the explicit `for`-loop —
it's the same shape the `Collection` branch already uses.
-
----
-
-## 5. Acceptance criteria
-
-- New commons-side unit test (Phase 1) reproduces the gap on pre-fix code,
passes on post-fix code.
-- All four Bug-#7b parsers stay green: `mvn -pl juneau-utest -am
-Dtest='Hjson*Test,Hocon*Test,Proto*Test,Bson*Test' test`.
-- `EnumFormat_RoundTrip_Test` matrix stays at **2268 / 0** (the headline
`EnumFormat` round-trip count after Wave-3 Bug #7b closure in TODO-57).
-- `./scripts/test.py` clean across the rest of the suite.
-- Phase 4 audit pass produces a written summary appended to this plan (either
"no sibling gaps" or a list of follow-up TODO items).
-
----
-
-## 6. Out of scope
-
-- The four per-parser Bug #7b fixes remain in place — see Phase 3 disposition.
-- Other latent commons-side type-coercion gaps — each gets its own follow-up
TODO.
-- Generic-arity changes to `BeanPropertyMeta` — fix is limited to the `Map<K,
V>` key inspection.
-- Performance optimization of `convertToType` itself — orthogonal.
-- Sibling shapes (`Set<E>`, `Iterable<E>`, generic-typed arrays) — flagged
under Open Question 3, but each gets its own plan if Phase 4 surfaces them.
-
----
-
-## 7. Related plans / references
-
-- **`todo/TODO-57-format-round-trip-tests.md`** — parent matrix, particularly:
- - **Bug #7b** (around lines 220–233) — the four parser-side fixes that
render this commons-side gap unreachable from any tested parser.
- - **Open Question 6** (around line 703) — the latent-gap flag this plan
exists to close.
-- **The four per-parser Bug #7b fix sites** (for reference, not to be
modified):
- -
`juneau-core/juneau-marshall/src/main/java/org/apache/juneau/hjson/HjsonParserSession.java`
— `propertyType` widening + key-coercion thread-through.
- -
`juneau-core/juneau-marshall/src/main/java/org/apache/juneau/hocon/HoconParserSession.java`
— `hoconToMap` type-threading.
- -
`juneau-core/juneau-marshall/src/main/java/org/apache/juneau/proto/ProtoParserSession.java`
— `convertValue` / `convertMapToType` key type propagation.
- -
`juneau-core/juneau-marshall/src/main/java/org/apache/juneau/bson/BsonParserSession.java`
— `parseDocument` `convertAttrToType` with typed key class.
-- **The JSON-family working pattern** (for comparison):
- -
`juneau-core/juneau-marshall/src/main/java/org/apache/juneau/json/JsonParserSession.java`
— `parseIntoMap2` (line 559) accepts `ClassMeta<K> keyType` and calls
`convertAttrToType(m, currAttr, keyType)` at line 598 before the entry lands in
the bean property.
-- **`todo/TODO-30-classmeta-to-commons.md`** — pending move of `ClassMeta`
itself into commons. If that move lands first, the `rawTypeMeta.getKeyType()`
call in this plan's fix is unchanged (the API surface is the same on either
side of the move); if this plan lands first, no coordination needed.
-- **`todo/TODO-5` (referenced in TODO-30)** — the bean-runtime types move that
brought `BeanPropertyMeta` into `juneau-commons`. The site this plan modifies
is post-TODO-5, in its new commons-side home.
diff --git a/todo/TODO.md b/todo/TODO.md
index 4a9a5f70db..1afe645949 100644
--- a/todo/TODO.md
+++ b/todo/TODO.md
@@ -3,8 +3,6 @@
- [TODO-7] Decouple `juneau-rest-common` from `juneau-marshall` by breaking
the compile dependency so REST annotations and beans can be used without
pulling in the full serialization stack. See
`todo/TODO-7-decouple-rest-common-from-marshall.md`.
-- [TODO-14] Close the latent map-key coercion gap in
`BeanPropertyMeta.setPropertyValue` (`needsConversion` predicate inspects only
entry values, not entry keys; key-side `convertToType` call missing). Currently
unreachable from any tested parser thanks to the per-parser Bug #7b fixes, but
a defense-in-depth backstop. See
`todo/TODO-14-beanpropertymeta-map-key-coercion.md`.
-
- [TODO-17] Audit 9.2.x changes (juneau-docs release notes 9.2.0 / 9.5.0 + git
history since 9.1.0) for breaking changes and populate the v9.5 Migration Guide
at juneau-docs/pages/topics/23.01.V9.5-migration-guide.md with Old→New rows for
each. Focus on removed APIs, renamed annotations/classes/methods, changed
default behaviors, and any annotation-attribute semantics changes.
- [TODO-18] Investigate possible useful features to add to juneau-rest-server.