Skip to content

Experiment for achieving Jackson2+3 support - #382

Closed
Pfeil wants to merge 25 commits into
developmentfrom
jackson-free-core
Closed

Pfeil wants to merge 25 commits into
developmentfrom
jackson-free-core

Conversation

@Pfeil

@Pfeil Pfeil commented Oct 22, 2026

Copy link
Copy Markdown
Member

This is an experimental (and AI driven) transformation of the library, not ready for use yet. This may not find its way into the library! The main reason for the PR is to make use of CI checks.

Problem

  • Related to Support Jackson 3 #349
  • Jackson 2 types are used both internally as well as exposed via the API (not too prominently, but it is).
  • We do not provide Jackson 2 <-> 3 conversion helpers, and upgrading will break things for the (current) Jackson 2 majority and blocking our upgrade progress.

Goal

  • make the API jackson independent (replacing the types with our own)
  • provide helpers to convert the replacing types, if required, from/to jackson 2 and 3
  • avoid at the same time to pull both dependencies into the project if the users only want one
  • as jackson is also needed internally, have adapters that allow the users to choose which one to use

This implementation

  • Splits the library into a core, a jackson 2 backend, and a jackson 3 backend
  • backends not only pull the dependency in and use it internally in the core, but also provide conversion helpers for users
  • The users just depending on ro-crate-java would get the jackson2 as before, but there are API changes. A migration document is provided (needs proper refinement).
  • The users who want helpers for jackson 3 and no jackson 2 dependency in their project can use ro-crate-java-jackson3

Missing / TODO

  • Evaluate the publication process etc, ensuring and inspecting if the ro-crate-java and ro-crate-java-jackson3 packages would work as I intend.
  • Code review
  • Migration document overhaul and review
  • ...

Discussion

  • Maintenance cost should stay low
  • Project complexity increased -> is it worth the goal? Is there a simpler way?

Pfeil added 23 commits October 14, 2026 10:00
Lock the library's observable behavior before the Jackson-free-core
conversion:
- byte-level golden files for getJsonMetadata() of freshly read crates,
  canonicalized by sorting @graph entities by @id (entity order is
  unspecified in the library and varies across JVM runs)
- structural write/read idempotence check reusing the suite's comparator
- data-entity set must survive the round trip unchanged

Goldens can be regenerated with: ./gradlew test -Drocrate.golden.update=true
(the rocrate.* system properties are now propagated to the test JVM)
Introduce edu.kit.datamanager.ro_crate.json:
- JsonValue tree model (JsonObject, JsonArray, JsonString, JsonNumber,
  JsonBoolean, JsonNull, JsonMissing) mirroring the Jackson node API the
  library uses: same method names (put/set/get/path/fields/asText/...),
  insertion order, deep structural equality
- semantics verified empirically against Jackson 2.22.1 and locked by
  parity tests: class-strict number equality, order-insensitive object
  equality, MissingNode.asText(default) semantics, readTree keeps
  single-element arrays
- JsonAdapter SPI (parse/write/node factories) with Jackson2JsonAdapter
  discovered via ServiceLoader; serialization delegated to Jackson 2 so
  output stays byte-identical to the pre-conversion library

No existing library code is changed yet.
The library core no longer touches Jackson types: all production code
now works on the neutral JSON model from edu.kit.datamanager.ro_crate.json.
Entity serialization is done by explicit toJson() implementations,
replacing the Jackson bean serialization with its custom serializers
(ObjectNodeSerializer, HasPartSerializer) and the MyObjectMapper
configuration, which are all removed.

The Jackson semantics the core relied on (scalar unwrapping of
single-element sets, MissingNode/NullNode asText behaviour, object
equality ignoring order) are now mirrored by the neutral model.

BREAKING CHANGE: public API types change from Jackson tree nodes to the
neutral JSON model. Users of the historical coordinates continue via
the ro-crate-java-jackson2 adapter, which keeps Jackson 2 underneath.
The shared test helper now compares JSON via the string form of the
neutral trees (produced by the active adapter), so json-compare no
longer receives Jackson node objects.
All tests now build their expectations with the neutral JSON model.
The golden parity test intentionally keeps Jackson 2 as the reference,
locking the adapter output against the pre-split library output.
Three behavior mismatches between the neutral model and Jackson, all
verified empirically against Jackson 2.22.1:

- JsonValue.iterator() mirrors Jackson node iteration: scalars iterate
  nothing, objects their values, arrays their elements
- JsonArray.add(null) stores JsonNull (Jackson's ArrayNode.add does the
  same: a null argument becomes a NullNode)
- entity @type mirrors the former valueToTree(types) with the legacy
  mapper's WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED: a single type serializes
  to a plain string, multiple types to an array

Full test suite (314 tests incl. golden round-trip and adapter parity
tests) passes.
Restructure the single Gradle project into three modules:

- ro-crate-java-core: the Jackson-free domain plus the neutral JSON model
  and the JsonAdapter/validation SPIs. The main compile classpath has no
  JSON library dependency (enforced by the module boundary). The JSON
  backend and the JSON-schema validation strategies are discovered via
  ServiceLoader (new Validation facade in core).
- ro-crate-java-jackson2: the Jackson 2 adapter and networknt 1.5.x
  validation. Publishes under the historical coordinates
  (edu.kit.datamanager:ro-crate-java) so existing consumers keep
  resolving the same artifact, transitively pulling the core.
- ro-crate-java-jackson3: new backend on tools.jackson 3.2.1 (the
  version the networknt 3.0.x validator requires), with a
  Jackson3JsonAdapter, registered via ServiceLoader. This makes the
  library usable from Jackson 3 applications.

Publishing/signing/release configuration moved into shared gradle
scripts (module-common, module-publishing); the root project now only
coordinates the release plugin and Nexus publishing.
The networknt 3.x validator redesigned its API (SchemaRegistry, Schema
and Error instead of JsonSchemaFactory, JsonSchema and ValidationMessage);
the jackson3 module implements both validation strategies on it. Schemas
load via the classpath scheme, which networknt 3.x requires for resolving
the relative refs between the schema files (verified empirically;
file-URI loading fails in networknt 3.0.8).

All three modules pass the full test suite, including the golden-file
parity test proving the Jackson 3 backend produces byte-identical
metadata to the original Jackson 2 library.
@coderabbitai

coderabbitai Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@Pfeil
Pfeil marked this pull request as draft October 22, 2026 07:53
@Pfeil
Pfeil changed the base branch from main to development October 22, 2026 07:53
Pfeil added 2 commits October 22, 2026 12:31
Resolve the build.gradle conflict in favor of the modular build; re-apply
development's version updates where the packages live now:

- net.researchgate.release 3.2.0 stays in the root build.gradle
- jackson 2.22.4 and io.freefair.maven-publish-java 9.8.2 moved to
  jackson2/build.gradle
- json-compare 8.4, slf4j-jdk14 2.0.20 and freemarker 2.3.35 moved to
  core/build.gradle
- GitHub Actions bumps (codeql 4.39.0, setup-java 6.1.0, gradle/actions
  6.4.0, upload-artifact 7.0.2, codecov 7.1.1) taken from development

Validated with ./gradlew check in the devcontainer image (JDK 25): all
modules build and test, including the examples parity suite.
The WorkflowHubTest reads /crates/workflowhub/workflow-109-5.crate.zip
from the test classpath, but the file was never tracked: the global
'*.zip' ignore rule swallowed it, so it only survived as a stale copy
in bin/test from before the module split. Any fresh checkout (CI,
devcontainer) fails the test with a NullPointerException from the
missing resource.

Add a negation for test fixture crates under core/src/test/resources
and commit the zip from that leftover copy. Verified in the
devcontainer: ./gradlew :core:test --tests '*WorkflowHubTest*' passes.
@Pfeil Pfeil closed this Oct 22, 2026
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