Skip to content

Separate a nested implementation with a dot, not a dollar - #4

Merged
pjfanning merged 1 commit into
mainfrom
dotted-nested-names
Aug 23, 2026
Merged

pjfanning merged 1 commit into
mainfrom
dotted-nested-names

Conversation

@pjfanning

Copy link
Copy Markdown
Owner

Following jackson-module-scala#839.

An implementation nested in a class other than the root was written {"@type":"Boxed$Same"}. It is now {"@type":"Boxed.Same"} — the way jackson-databind separates a name from what encloses it. This changes the JSON, so it is a wire-format change, not a refactor.

public sealed interface Dup extends SealedPolymorphismSupport permits Boxed.Same, Nested.Same {}

class Boxed  { record Same(int v)    implements Dup {} }   // {"@type":"Boxed.Same","v":1}
class Nested { record Same(String v) implements Dup {} }   // {"@type":"Nested.Same","v":"x"}

Only real nesting becomes a dot

A $ is a legal character in a Java identifier, so a class can carry one in a name of its own making. The enclosing-class chain is what tells the two apart: a class with a $ in its own name has no enclosing class at that position, so that $ is left alone.

This is the counterpart of the Scala module's concern with :: compiling to $colon$colon. There is a fixture named Odd$Name asserting such a name survives intact:

{"expr":{"@type":"Odd$Name","head":1,"tail":2}}
{"expr":{"@type":"Grouped.Inner","v":5}}

Simpler here than in Scala

The Scala module has to convert the dot back to a $ before looking a name up, because it resolves by rebuilding candidate class names and calling Class.forName. Nothing of the sort is needed here — names are resolved against the hierarchy's own table, built from the PermittedSubclasses attribute, so the derived name is simply the map key. A dot in a @type still cannot address a package, and is still never handed to Class.forName.

So this PR changes typeNameFor and nothing else in the main sources.

Tests

93 total. Three existing name-shape assertions updated (SealedPolymorphismTest, SealedTypesTest, MixInTest), plus three new ones: the dot for an enclosing class, a $ that is part of the class name surviving, and typeNameFor covering both directly.

🤖 Generated with Claude Code

https://claude.ai/code/session_012hxYY9KPDAMXADWzjBHNV4

Following jackson-module-scala#839. An implementation nested in a class
other than the root was written {"@type":"Boxed$Same"}; it is now
{"@type":"Boxed.Same"}, which is how jackson-databind separates a name
from what encloses it. The JVM writes that boundary as a $, so the two
spellings differ and this is a change to the JSON.

Only a real nesting boundary becomes a dot. A $ is a legal character in
a Java identifier, so a class can carry one in a name of its own making,
and the enclosing class chain is what tells the two apart - a class with
a $ in its own name has no enclosing class at that position. This is the
counterpart of the Scala module's concern with `::` compiling to
$colon$colon, and there is now a fixture named Odd$Name that asserts such
a name survives intact.

The Scala module has to convert the dot back to a $ before looking a name
up, since it resolves by rebuilding class names and calling Class.forName.
Nothing of the sort is needed here: names are resolved against the
hierarchy's own table, built from the PermittedSubclasses attribute, so
the derived name is simply the key. A dot in a @type still cannot address
a package.

93 tests.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012hxYY9KPDAMXADWzjBHNV4
@pjfanning
pjfanning merged commit c8f8a9f into main Aug 23, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant