Skip to content

feat(jackson3): add annotated domain-to-resource mapping - #31

Merged
kazemek merged 6 commits into
mainfrom
feat/jackson3-domain-resource-mapping
Aug 2, 2026
Merged

kazemek merged 6 commits into
mainfrom
feat/jackson3-domain-resource-mapping

Conversation

@kazemek

@kazemek kazemek commented Aug 1, 2026 •

Copy link
Copy Markdown
Collaborator

Map @JsonApiResource POJOs/records to resource objects via Jackson 3's logical property model. JsonApiMapper derives from caller's mapper via rebuild() and caches ResourceMapping by type + config identity.

Summary by CodeRabbit

  • New Features
    • Added domain-object mapping to JSON:API resources and documents, including single resources, collections, relationships, attributes, and document metadata.
    • Added configurable identifier conversion with a default conversion for common identifier types.
    • Added clear mapping diagnostics for invalid annotations, missing identifiers, naming conflicts, and unsupported values.
    • Preserved existing serialization behavior when creating resource mappers.
  • Documentation
    • Updated project and module documentation with mapping capabilities, supported rules, usage guidance, and conformance status.
  • Refactor
    • Improved validation package organization and internal documentation.

Map @JsonApiResource POJOs/records to resource objects via Jackson 3's
logical property model. JsonApiMapper derives from caller's mapper via
rebuild() and caches ResourceMapping by type + config identity.
@coderabbitai

coderabbitai Bot commented Aug 1, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e8b733ec-92b6-428f-bd96-1bf6ed1fa3f2

📥 Commits

Reviewing files that changed from the base of the PR and between 29eb528 and 993096d.

📒 Files selected for processing (6)
  • .agentWork/milestones/README.md
  • .agentWork/milestones/phase-2-2-domain-resource-mapping.md
  • build-logic/src/main/kotlin/jsonapi-java-spotless.gradle.kts
  • docs/conformance.md
  • jsonapi-java-jackson3/src/main/java/io/github/kazemek/jsonapi/jackson3/internal/DomainResourceWriter.java
  • jsonapi-java-jackson3/src/main/java/io/github/kazemek/jsonapi/jackson3/internal/MappingDefinitionResolver.java
🚧 Files skipped from review as they are similar to previous changes (3)
  • jsonapi-java-jackson3/src/main/java/io/github/kazemek/jsonapi/jackson3/internal/MappingDefinitionResolver.java
  • docs/conformance.md
  • jsonapi-java-jackson3/src/main/java/io/github/kazemek/jsonapi/jackson3/internal/DomainResourceWriter.java

📝 Walkthrough

Walkthrough

The Jackson 3 module now maps annotated domain objects to JSON:API resources and documents. The change adds mapping APIs, identifier conversion, diagnostics, relationship linkage handling, mapping-definition caching, validation relocation, tests, and project documentation.

Changes

Domain resource mapping

Layer / File(s) Summary
Relocate member-name validation
jsonapi-java-core/src/main/java/..., jsonapi-java-core/src/test/...
Moves MemberNames into the validation package and updates imports, documentation, and tests.
Add public mapping API
jsonapi-java-jackson3/src/main/java/io/github/kazemek/jsonapi/jackson3/*
Adds resource mapper APIs, document envelopes, identifier conversion, diagnostics, exceptions, mapper factories, and document conversion.
Resolve and cache mapping definitions
jsonapi-java-jackson3/src/main/java/.../internal/*
Adds property roles, mapping records, annotation resolution, name and role validation, and concurrent mapping-definition caching.
Write resources and linkage
jsonapi-java-jackson3/src/main/java/.../internal/DomainResourceWriter.java
Maps identifiers, attributes, optional values, arrays, collections, iterables, and relationship linkage into JSON:API resources.
Validate mapping behavior
jsonapi-java-jackson3/src/test/*
Adds coverage for diagnostics, identifier conversion, mapper isolation, documents, relationships, Jackson features, optional values, and custom serialization.
Update documentation and exclusions
README.md, docs/conformance.md, jsonapi-java-jackson3/README.md, .gitignore, build-logic/...
Marks Phase 2.2 mapping as supported, documents the new API, and adds Kotlin, Eclipse, and binary artifact exclusions.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant JsonApiResourceMapper
  participant DomainResourceWriter
  participant MappingDefinitionCache
  participant ResourceObject
  JsonApiResourceMapper->>DomainResourceWriter: map domain resource
  DomainResourceWriter->>MappingDefinitionCache: resolve cached mapping
  MappingDefinitionCache-->>DomainResourceWriter: return ResourceMapping
  DomainResourceWriter->>ResourceObject: build resource object
  ResourceObject-->>JsonApiResourceMapper: return mapped resource
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.53% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding annotated domain-to-resource mapping for Jackson 3.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/jackson3-domain-resource-mapping

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
jsonapi-java-jackson3/src/test/java/io/github/kazemek/jsonapi/jackson3/testmodel/Comment.java (1)

1-8: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Annotate author as a relationship.

Article.author uses @JsonApiRelationship, but Comment.author does not. Add the annotation so direct mapping classifies author as a relationship rather than an attribute.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@jsonapi-java-jackson3/src/test/java/io/github/kazemek/jsonapi/jackson3/testmodel/Comment.java`
around lines 1 - 8, Update the Comment record’s author component to include
`@JsonApiRelationship`, matching Article.author, so direct mapping classifies
author as a relationship instead of an attribute.
🧹 Nitpick comments (2)
jsonapi-java-jackson3/src/main/java/io/github/kazemek/jsonapi/jackson3/internal/DomainResourceWriter.java (2)

124-186: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Align @JsonApiResource validation between to-one and to-many relationship paths.

extractToManyLinkage explicitly calls checkResourceAnnotation(elementClass) (line 179) and raises UNSUPPORTED_RELATIONSHIP_COLLECTION_TYPE when the element type lacks @JsonApiResource. extractToOneLinkage has no equivalent explicit check; a missing @JsonApiResource on a to-one target only surfaces indirectly through extractIdentifier → cache.resolve → MISSING_RESOURCE_ANNOTATION. The same root misconfiguration produces two different diagnostics depending on relationship cardinality, which makes error handling and diagnostics-based tests inconsistent.

Consider calling checkResourceAnnotation(value.getClass()) in extractToOneLinkage before delegating to extractIdentifier, so both cardinalities raise the same class of diagnostic for the same root cause.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@jsonapi-java-jackson3/src/main/java/io/github/kazemek/jsonapi/jackson3/internal/DomainResourceWriter.java`
around lines 124 - 186, The to-one relationship path lacks the explicit
resource-annotation validation used by to-many relationships. Update
extractToOneLinkage to call checkResourceAnnotation on the unwrapped value’s
class before extractIdentifier, while preserving the existing ResourceIdentifier
and RelationshipData handling.

248-266: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Classify custom Iterable implementations as to-many.

Replace hasRawClass(Iterable.class) with isTypeOrSubTypeOf(Iterable.class). Jackson can represent custom iterable types as non-collection-like JavaType instances.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@jsonapi-java-jackson3/src/main/java/io/github/kazemek/jsonapi/jackson3/internal/DomainResourceWriter.java`
around lines 248 - 266, Update isToManyType in DomainResourceWriter to use
isTypeOrSubTypeOf(Iterable.class) instead of hasRawClass(Iterable.class), so
custom Iterable implementations are classified as to-many while preserving the
existing array and collection-like checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@jsonapi-java-jackson3/README.md`:
- Around line 44-46: Update the prefixer IdentifierConverter example passed to
JsonApiJackson3.resourceMapper so it returns null for a null idValue before
applying the "urn:" prefix; preserve the existing prefixed conversion for
non-null identifiers.

In
`@jsonapi-java-jackson3/src/main/java/io/github/kazemek/jsonapi/jackson3/internal/DomainResourceWriter.java`:
- Around line 88-98: Update buildAttributes to detect Optional-valued properties
before inserting them into the attributes map, unwrap present values, and skip
entries whose Optional is empty so they are not serialized as null; preserve
existing conversion for non-Optional and present Optional values, and add an
assertion that subtitle is absent.

In
`@jsonapi-java-jackson3/src/main/java/io/github/kazemek/jsonapi/jackson3/internal/MappingDefinitionResolver.java`:
- Around line 89-108: Update classifyProperties so a propertyDefinition with no
accessor is not silently skipped when it has an explicit role annotation;
resolve or detect its annotated role and raise the established MappingDiagnostic
for a missing accessor, including the property context. Preserve skipping only
for unannotated properties and keep normal MappingProperty classification
unchanged when an accessor exists.
- Around line 157-231: Update validateJsonApiName to reject JSON:API-reserved
member names "id" and "type" in addition to invalid names, including when
supplied through explicit annotations. Raise the appropriate mapping diagnostic
before mapping resolution completes, while preserving the existing role-specific
validation behavior.

In
`@jsonapi-java-jackson3/src/test/groovy/io/github/kazemek/jsonapi/jackson3/ResourceMappingJacksonFeaturesSpec.groovy`:
- Around line 178-289: Update the relationship collection classification logic
exercised by MixedRelEntity so unsupported elements consistently produce
UNSUPPORTED_RELATIONSHIP_VALUE regardless of their position relative to a
ResourceIdentifier. Ensure validation examines the complete collection or
otherwise preserves the same diagnostic for both element orders, while retaining
valid linkage handling for collections containing null and ResourceIdentifier
values.

---

Outside diff comments:
In
`@jsonapi-java-jackson3/src/test/java/io/github/kazemek/jsonapi/jackson3/testmodel/Comment.java`:
- Around line 1-8: Update the Comment record’s author component to include
`@JsonApiRelationship`, matching Article.author, so direct mapping classifies
author as a relationship instead of an attribute.

---

Nitpick comments:
In
`@jsonapi-java-jackson3/src/main/java/io/github/kazemek/jsonapi/jackson3/internal/DomainResourceWriter.java`:
- Around line 124-186: The to-one relationship path lacks the explicit
resource-annotation validation used by to-many relationships. Update
extractToOneLinkage to call checkResourceAnnotation on the unwrapped value’s
class before extractIdentifier, while preserving the existing ResourceIdentifier
and RelationshipData handling.
- Around line 248-266: Update isToManyType in DomainResourceWriter to use
isTypeOrSubTypeOf(Iterable.class) instead of hasRawClass(Iterable.class), so
custom Iterable implementations are classified as to-many while preserving the
existing array and collection-like checks.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 52b7d48a-d79f-4de1-a0e4-1eacdc18e595

📥 Commits

Reviewing files that changed from the base of the PR and between 379346f and a0b3527.

📒 Files selected for processing (55)
  • .gitignore
  • README.md
  • docs/conformance.md
  • jsonapi-java-core/src/main/java/io/github/kazemek/jsonapi/core/internal/AdditionalMembers.java
  • jsonapi-java-core/src/main/java/io/github/kazemek/jsonapi/core/internal/package-info.java
  • jsonapi-java-core/src/main/java/io/github/kazemek/jsonapi/core/model/Attributes.java
  • jsonapi-java-core/src/main/java/io/github/kazemek/jsonapi/core/model/JsonApiDocument.java
  • jsonapi-java-core/src/main/java/io/github/kazemek/jsonapi/core/model/Links.java
  • jsonapi-java-core/src/main/java/io/github/kazemek/jsonapi/core/model/Meta.java
  • jsonapi-java-core/src/main/java/io/github/kazemek/jsonapi/core/model/Relationship.java
  • jsonapi-java-core/src/main/java/io/github/kazemek/jsonapi/core/model/Relationships.java
  • jsonapi-java-core/src/main/java/io/github/kazemek/jsonapi/core/model/ResourceIdentifier.java
  • jsonapi-java-core/src/main/java/io/github/kazemek/jsonapi/core/model/ResourceObject.java
  • jsonapi-java-core/src/main/java/io/github/kazemek/jsonapi/core/validation/JsonApiDocumentValidator.java
  • jsonapi-java-core/src/main/java/io/github/kazemek/jsonapi/core/validation/MemberNames.java
  • jsonapi-java-core/src/main/java/io/github/kazemek/jsonapi/core/validation/package-info.java
  • jsonapi-java-core/src/test/groovy/io/github/kazemek/jsonapi/core/validation/MemberNamesSpec.groovy
  • jsonapi-java-jackson3/README.md
  • jsonapi-java-jackson3/build.gradle.kts
  • jsonapi-java-jackson3/src/main/java/io/github/kazemek/jsonapi/jackson3/DocumentEnvelope.java
  • jsonapi-java-jackson3/src/main/java/io/github/kazemek/jsonapi/jackson3/IdentifierConverter.java
  • jsonapi-java-jackson3/src/main/java/io/github/kazemek/jsonapi/jackson3/JsonApiJackson3.java
  • jsonapi-java-jackson3/src/main/java/io/github/kazemek/jsonapi/jackson3/JsonApiMappingException.java
  • jsonapi-java-jackson3/src/main/java/io/github/kazemek/jsonapi/jackson3/JsonApiResourceMapper.java
  • jsonapi-java-jackson3/src/main/java/io/github/kazemek/jsonapi/jackson3/MappingDiagnostic.java
  • jsonapi-java-jackson3/src/main/java/io/github/kazemek/jsonapi/jackson3/internal/DomainResourceWriter.java
  • jsonapi-java-jackson3/src/main/java/io/github/kazemek/jsonapi/jackson3/internal/MappingDefinitionCache.java
  • jsonapi-java-jackson3/src/main/java/io/github/kazemek/jsonapi/jackson3/internal/MappingDefinitionResolver.java
  • jsonapi-java-jackson3/src/main/java/io/github/kazemek/jsonapi/jackson3/internal/MappingProperty.java
  • jsonapi-java-jackson3/src/main/java/io/github/kazemek/jsonapi/jackson3/internal/PropertyRole.java
  • jsonapi-java-jackson3/src/main/java/io/github/kazemek/jsonapi/jackson3/internal/ResourceMapping.java
  • jsonapi-java-jackson3/src/main/java/io/github/kazemek/jsonapi/jackson3/internal/package-info.java
  • jsonapi-java-jackson3/src/main/java/io/github/kazemek/jsonapi/jackson3/package-info.java
  • jsonapi-java-jackson3/src/test/groovy/io/github/kazemek/jsonapi/jackson3/DomainResourceWriterDiagnosticsSpec.groovy
  • jsonapi-java-jackson3/src/test/groovy/io/github/kazemek/jsonapi/jackson3/IdentifierConversionSpec.groovy
  • jsonapi-java-jackson3/src/test/groovy/io/github/kazemek/jsonapi/jackson3/ResourceMapperIsolationSpec.groovy
  • jsonapi-java-jackson3/src/test/groovy/io/github/kazemek/jsonapi/jackson3/ResourceMapperSpec.groovy
  • jsonapi-java-jackson3/src/test/groovy/io/github/kazemek/jsonapi/jackson3/ResourceMappingJacksonFeaturesSpec.groovy
  • jsonapi-java-jackson3/src/test/java/io/github/kazemek/jsonapi/jackson3/testmodel/Article.java
  • jsonapi-java-jackson3/src/test/java/io/github/kazemek/jsonapi/jackson3/testmodel/ArticleWithArray.java
  • jsonapi-java-jackson3/src/test/java/io/github/kazemek/jsonapi/jackson3/testmodel/ArticleWithFormattedTitle.java
  • jsonapi-java-jackson3/src/test/java/io/github/kazemek/jsonapi/jackson3/testmodel/ArticleWithOptional.java
  • jsonapi-java-jackson3/src/test/java/io/github/kazemek/jsonapi/jackson3/testmodel/ArticleWithOptionalId.java
  • jsonapi-java-jackson3/src/test/java/io/github/kazemek/jsonapi/jackson3/testmodel/ArticleWithOptionalRelationship.java
  • jsonapi-java-jackson3/src/test/java/io/github/kazemek/jsonapi/jackson3/testmodel/ArticleWithSet.java
  • jsonapi-java-jackson3/src/test/java/io/github/kazemek/jsonapi/jackson3/testmodel/BaseBlog.java
  • jsonapi-java-jackson3/src/test/java/io/github/kazemek/jsonapi/jackson3/testmodel/BlogWithJsonProperty.java
  • jsonapi-java-jackson3/src/test/java/io/github/kazemek/jsonapi/jackson3/testmodel/Comment.java
  • jsonapi-java-jackson3/src/test/java/io/github/kazemek/jsonapi/jackson3/testmodel/ConventionalId.java
  • jsonapi-java-jackson3/src/test/java/io/github/kazemek/jsonapi/jackson3/testmodel/CreatorBasedArticle.java
  • jsonapi-java-jackson3/src/test/java/io/github/kazemek/jsonapi/jackson3/testmodel/ExtendedBlog.java
  • jsonapi-java-jackson3/src/test/java/io/github/kazemek/jsonapi/jackson3/testmodel/FormattedTitle.java
  • jsonapi-java-jackson3/src/test/java/io/github/kazemek/jsonapi/jackson3/testmodel/Person.java
  • jsonapi-java-jackson3/src/test/java/io/github/kazemek/jsonapi/jackson3/testmodel/Tag.java
  • jsonapi-java-jackson3/src/test/java/io/github/kazemek/jsonapi/jackson3/testmodel/TitleSerializer.java
💤 Files with no reviewable changes (1)
  • jsonapi-java-core/src/main/java/io/github/kazemek/jsonapi/core/validation/JsonApiDocumentValidator.java

Comment thread jsonapi-java-jackson3/README.md
Omit empty Optional attributes, fail early on missing accessors and
reserved member names, and make mixed to-many diagnostics order-independent.
@kazemek

kazemek commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed the actionable CodeRabbit findings in 29eb528:

Fixed

  • Empty Optional attributes omitted (absent vs JSON null)
  • MISSING_ACCESSOR for role-annotated properties with no readable accessor
  • Reserved "id"/"type" rejected at mapping resolve time
  • Order-independent mixed to-many diagnostics
  • Null-safe IdentifierConverter README example
  • Comment.author annotated @JsonApiRelationship
  • isToManyType uses isTypeOrSubTypeOf(Iterable.class)

Skipped (intentionally)

  • Aligning to-one with checkResourceAnnotation — would misuse UNSUPPORTED_RELATIONSHIP_COLLECTION_TYPE for to-one / raw Object content types; to-one without @JsonApiResource already fails via MISSING_RESOURCE_ANNOTATION through resolve
  • Docstring coverage autofix / stacked-PR suggestions — public API is already documented; Sonar QG passed

coderabbitai[bot]
coderabbitai Bot previously approved these changes Aug 2, 2026
kazemek added 4 commits August 2, 2026 20:04
Keep Spotless from formatting Eclipse/IntelliJ bin trees that sit outside
the Gradle build directory.
Reflect that annotated domain-to-resource mapping is delivered, not deferred.
Break up dense linkage and role-resolution logic into focused helpers so
the mapping engine stays easy to follow without changing behavior.
Avoid rescanning property members for name overrides after role
resolution, and mark Phase 2.2 complete in the milestone index.
@sonarqubecloud

sonarqubecloud Bot commented Aug 2, 2026

Copy link
Copy Markdown

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