Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
90e5048
Add golden round-trip tests as conversion safety net
Pfeil Oct 14, 2026
996e9d1
Merge jfc/golden-tests: golden round-trip safety net (Phase 0)
Pfeil Oct 14, 2026
7a91f3a
Add neutral Jackson-free JSON tree model and adapter SPI
Pfeil Oct 14, 2026
cc7a896
Merge jfc/neutral-json-model: neutral JSON model + adapter SPI (Phase…
Pfeil Oct 14, 2026
86f840e
refactor!: port main source to the neutral JSON model
Pfeil Oct 14, 2026
91bdfac
test: port HelpFunctions to the neutral JSON model
Pfeil Oct 14, 2026
5e9794b
test: port test suite to the neutral JSON model
Pfeil Oct 15, 2026
d3a3e6a
fix: mirror Jackson semantics found by the test suite
Pfeil Oct 15, 2026
ca34fb9
cleanup(core): remove unused imports from Graph
Pfeil Oct 15, 2026
1461ded
build: split the build into core, jackson2 and jackson3 modules
Pfeil Oct 18, 2026
7377735
feat(jackson3): add Jackson 3 validation backends with golden parity
Pfeil Oct 18, 2026
ae92bf0
cleanup: remove unused imports left over from the neutral-model port
Pfeil Oct 19, 2026
40c3290
chore: merge port-main into jackson-free-core
Pfeil Oct 19, 2026
1a4fbe9
docs: document core/jackson2/jackson3 module layout and Jackson 3 usage
Pfeil Oct 19, 2026
1d242f8
docs: add migration guide for the Jackson-free core
Pfeil Oct 20, 2026
3e328ca
docs: describe when validation runs and how to provide custom validators
Pfeil Oct 20, 2026
6d5c0a7
docs: drop confusing guidance on registering custom validators
Pfeil Oct 20, 2026
97b5a9b
test: add examples module comparing released 2.1.2 with current backends
Pfeil Oct 20, 2026
7b4a203
docs: document the examples module commands
Pfeil Oct 20, 2026
b2b0d68
test: add read, modify, write and validate examples to the parity suite
Pfeil Oct 20, 2026
b2d4636
cleanup(examples): compare all examples in the parity test and remove…
Pfeil Oct 20, 2026
cd88a58
fix(core): make backend discovery failures name the artifacts to add …
Pfeil Oct 20, 2026
5c90ccc
docs: state that the backend choice is automatic for historical consu…
Pfeil Oct 20, 2026
9a50884
merge: development into jackson-free-core
Pfeil Oct 22, 2026
096aaa5
test(core): track the WorkflowHub fixture crate zip
Pfeil Oct 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ fabric.properties
*.nar
*.ear
*.zip
# but track the test fixture crates
!core/src/test/resources/crates/**/*.zip
*.tar.gz
*.rar

Expand Down
239 changes: 239 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
# Migrating to the Jackson-free core

Starting with version 3, ro-crate-java is split into a Jackson-free core and
pluggable JSON backends. This guide explains what changes for your code when
you upgrade from the previous, Jackson-based 2.x API.

**Summary**

- The artifact coordinates for the Jackson 2 backend are unchanged
(`edu.kit.datamanager:ro-crate-java`), so your dependency declaration keeps
working.
- The high-level API (the `RoCrate` builder and the entity builders) is
source-compatible. Most code needs no changes at all.
- Code that used Jackson types from the library (`ObjectNode`, `JsonNode`,
`ArrayNode`, …) must switch to the neutral JSON model of the core. The
method names and semantics deliberately mirror Jackson, so this is mostly a
type rename.
- A few internal Jackson classes were removed (see
[Removed classes](#removed-classes)).

## Modules

| Module | Artifact | Purpose |
| ---------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------- |
| `core` | (internal, pulled in transitively) | Crate model, reader/writer, validation API, neutral JSON model — no JSON library dependency |
| `jackson2` | `edu.kit.datamanager:ro-crate-java` (historical coordinates) | Jackson 2 backend, for existing consumers |
| `jackson3` | `edu.kit.datamanager:ro-crate-java-jackson3` | Jackson 3 (`tools.jackson`) backend |

The core contains no JSON code itself. The backend module provides it, and the
core selects the backend at runtime with Java's `ServiceLoader`: the backend
jar carries a `META-INF/services` file that names its implementation classes,
and the core loads whatever is listed there. Exactly one backend must be on
the classpath. Depending on `edu.kit.datamanager:ro-crate-java` puts the
Jackson 2 backend there; `edu.kit.datamanager:ro-crate-java-jackson3` puts the
Jackson 3 backend there. The two core entry points that use it are `Json`
(JSON input and output) and `Validation` (schema validation).

## Type mapping

The Jackson types that used to leak into the public API are replaced by the
neutral JSON model from the **`core`** module, package
`edu.kit.datamanager.ro_crate.json`:

| Before (`com.fasterxml.jackson.databind…`) | After (`edu.kit.datamanager.ro_crate.json…`) |
| ------------------------------------------ | -------------------------------------------- |
| `JsonNode` | `JsonValue` |
| `ObjectNode` | `JsonObject` |
| `ArrayNode` | `JsonArray` |
| `TextNode` | `JsonString` |
| `IntNode` / `LongNode` / `DoubleNode` | `JsonNumber` |
| `BooleanNode` | `JsonBoolean` |
| `NullNode` | `JsonNull` |
| `MissingNode` | `JsonMissing` |

The neutral model is not a general-purpose JSON library: it implements exactly
the subset of Jackson's API the library needs, with the same method names and
semantics (`path()`, `get()`, `has()`, `asText()`, `fields()`, `size()`,
iteration, …).

## Changing the imports

Before (Jackson types came from `com.fasterxml.jackson.databind…`):

```java
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;

ObjectNode properties = entity.getProperties();
JsonNode name = properties.path("name");
```

After (the neutral model lives in the `core` module,
`edu.kit.datamanager.ro_crate.json`):

```java
import edu.kit.datamanager.ro_crate.json.JsonObject;
import edu.kit.datamanager.ro_crate.json.JsonValue;

JsonObject properties = entity.getProperties();
JsonValue name = properties.path("name");
```

## Common changes

### Properties

The method names are unchanged. Only the types in the signatures are now neutral.
Scalar `addProperty` overloads (`String`, `int`, `double`, `boolean`) exist on
the builders, so most property code needs no JSON types at all. The
`addProperty(String, JsonValue)` overload replaces the old
`addProperty(String, JsonNode)`:

```java
import edu.kit.datamanager.ro_crate.json.JsonString;
import edu.kit.datamanager.ro_crate.entities.data.FileEntity;

// was: builder.addProperty("description", new TextNode("long description"))
builder.addProperty("description", new JsonString("long description"));
```

### Building JSON trees

Before, you would have used Jackson's `ObjectMapper` or `JsonNodeFactory`.
Now use the factory methods of `edu.kit.datamanager.ro_crate.json.Json`
(**`core`** module):

```java
import edu.kit.datamanager.ro_crate.json.Json;
import edu.kit.datamanager.ro_crate.json.JsonArray;
import edu.kit.datamanager.ro_crate.json.JsonObject;

JsonObject obj = Json.objectNode()
.put("name", "Result table")
.put("contentSize", 26452);
JsonArray tags = Json.arrayNode().add("science").add("survey");

builder.setAllIfValid(obj); // was: setAll(ObjectNode); setAll is deprecated
```

### Parsing JSON

The `edu.kit.datamanager.ro_crate.objectmapper.MyObjectMapper` class was
removed. Parse with the core's `Json` entry point instead, which delegates to
the active backend:

```java
import edu.kit.datamanager.ro_crate.json.Json;
import edu.kit.datamanager.ro_crate.json.JsonValue;

JsonValue parsed = Json.parse(new java.io.File("ro-crate-metadata.json"));
```

## Converting between Jackson and the neutral model

If your code works with its own JSON trees, for example parsed with your own
`ObjectMapper`, convert them at the boundary to the neutral model. Both
backend modules provide the same helpers on their adapter class. With the
Jackson 2 backend (`edu.kit.datamanager:ro-crate-java`, package
`edu.kit.datamanager.ro_crate.json.jackson2`):

```java
import com.fasterxml.jackson.databind.JsonNode;
import edu.kit.datamanager.ro_crate.json.JsonValue;
import edu.kit.datamanager.ro_crate.json.jackson2.Jackson2JsonAdapter;

JsonNode jacksonNode = yourObjectMapper.readTree(json);
JsonValue neutral = Jackson2JsonAdapter.toNeutral(jacksonNode);
// and back, if needed:
JsonNode again = Jackson2JsonAdapter.toJackson(neutral);
```

With the Jackson 3 backend (`edu.kit.datamanager:ro-crate-java-jackson3`,
package `edu.kit.datamanager.ro_crate.json.jackson3`), the same methods exist
for Jackson 3 node types:

```java
import tools.jackson.databind.JsonNode;
import edu.kit.datamanager.ro_crate.json.JsonValue;
import edu.kit.datamanager.ro_crate.json.jackson3.Jackson3JsonAdapter;

JsonNode jacksonNode = yourJsonMapper.readTree(json);
JsonValue neutral = Jackson3JsonAdapter.toNeutral(jacksonNode);
// and back, if needed:
JsonNode again = Jackson3JsonAdapter.toJackson(neutral);
```

If your application uses Jackson 3, depend on the Jackson 3 artifact; the
classpath then contains only one Jackson version. The neutral model covers
only the subset of the JSON API the library needs, so do advanced tree
manipulation on your own nodes before converting.

## Validation

The core defines two validation interfaces:

- Crate-level: `edu.kit.datamanager.ro_crate.validation.ValidatorStrategy`,
implemented by the JSON-schema class of the backend module:
`edu.kit.datamanager.ro_crate.validation.JsonSchemaValidation` in the
`jackson2` module (historical fully qualified name, now with neutral JSON
types in its signatures), `edu.kit.datamanager.ro_crate.validation.jackson3.JsonSchemaValidation`
in the `jackson3` module.
- Entity-level: `edu.kit.datamanager.ro_crate.entities.validation.EntityValidationStrategy`,
which the entity builders use to check that properties follow the RO-Crate
rules (for example, that they are flat).

Like the JSON backends, the implementations are listed in `META-INF/services`
files and loaded with `ServiceLoader`. The core requires exactly one
implementation of each interface on the classpath; this is why a backend
module must be present.

### When validation runs

The crate-level validation runs automatically at three places, always with
the discovered backend implementation:

- when an `RoCrate` is created from a builder,
- when a crate is read from a folder or zip file,
- when a crate is saved with `CrateWriter`.

Validation problems are printed to the standard error stream; the operation
itself continues, because `validate()` only returns a boolean, which these
places ignore.

### Running validation explicitly

```java
import edu.kit.datamanager.ro_crate.validation.JsonSchemaValidation;
import edu.kit.datamanager.ro_crate.validation.Validator;

Validator validator = new Validator(
new JsonSchemaValidation("my-profile-schema.json"));
boolean valid = validator.validate(crate);
```

With the `jackson2` backend, import
`edu.kit.datamanager.ro_crate.validation.JsonSchemaValidation`; with the
`jackson3` backend, `edu.kit.datamanager.ro_crate.validation.jackson3.JsonSchemaValidation`.

### Providing your own validator

Implement the interface and pass it to the `Validator`:

```java
Validator validator = new Validator(crate -> {
// your checks; return false and report problems if the crate is invalid
return true;
});
validator.validate(crate);
```

## Removed classes

Internal classes without a replacement:

- `edu.kit.datamanager.ro_crate.objectmapper.MyObjectMapper` — use
`edu.kit.datamanager.ro_crate.json.Json.parse` / `Json.write` (see above).
- `edu.kit.datamanager.ro_crate.entities.serializers.HasPartSerializer` and
`edu.kit.datamanager.ro_crate.entities.serializers.ObjectNodeSerializer` —
serialization is handled by the backend's `JsonAdapter`.
69 changes: 60 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,79 @@ and avoiding crates which do not fully comply to the specification, at the same

- [Instructions for your build manager (e.g., Gradle, Maven, etc.)](https://central.sonatype.com/artifact/edu.kit.datamanager/ro-crate-java)
- [Quick-Start](#quick-start)
- [Migration guide: from the Jackson-based 2.x API to the Jackson-free core](MIGRATION.md)
- [JavaDoc Documentation](https://javadoc.io/doc/edu.kit.datamanager/ro-crate-java)
- [Related Publications](https://publikationen.bibliothek.kit.edu/publikationslisten/get.php?referencing=all&external_publications=kit&lang=de&format=html&style=kit-3lines-title_b-authors-other&consider_suborganizations=true&order=desc%20year&contributors=%5B%5B%5B%5D%2C%5B%22p20751.105%22%5D%5D%5D&title_contains=crate)

## Modules

The library is split into a Jackson-free core plus two JSON backend modules:

- **`core`** — the crate model, reader/writer and validation API, working on a
neutral JSON tree model (`edu.kit.datamanager.ro_crate.json`). It has no
dependency on any JSON library. JSON (de-)serialization and JSON-schema
validation are plugged in via the `JsonAdapter` and `ValidatorStrategy`
service-provider interfaces, discovered with Java's `ServiceLoader`:
exactly one backend must be on the classpath.
- **`jackson2`** — the Jackson 2 backend. It publishes under the historical
coordinates `edu.kit.datamanager:ro-crate-java`, so existing consumers keep
resolving the same artifact; it transitively pulls in the core.
- **`jackson3`** — the Jackson 3 backend (`tools.jackson`), published as
`edu.kit.datamanager:ro-crate-java-jackson3`.

To use the Jackson 3 backend, depend on the Jackson 3 artifact instead of the
historical one, e.g. in Gradle:

```groovy
implementation "edu.kit.datamanager:ro-crate-java-jackson3:<version>"
```

No further configuration is needed: the backend registers itself via
`META-INF/services`. At runtime, the core's JSON and validation entry points
(the `edu.kit.datamanager.ro_crate.json.Json` and
`edu.kit.datamanager.ro_crate.validation.Validation` classes) discover it with
`ServiceLoader`, so they use whichever backend is on the classpath. If you do
not care about the JSON backend, do nothing: keep depending on
`edu.kit.datamanager:ro-crate-java` as before — the backend comes with it.

## Build the library / documentation

- Building (with tests): `./gradlew clean build`
- Building (without tests): `./gradlew clean build -x test`
- Building with release profile: `./gradlew -Dprofile=release clean build`
- Doing a release: `./gradlew -Dprofile=release clean build release`
- Will prompt you about version number to use and next version number
- Will make a git tag which can later be used in a GitHub release
- A GitHub release will trigger the CI for publication. See also `.github/workflows/publishRelease.yml`.
- Will prompt you about version number to use and next version number
- Will make a git tag which can later be used in a GitHub release
- A GitHub release will trigger the CI for publication. See also `.github/workflows/publishRelease.yml`.
- Build documentation: `./gradlew javadoc`

On Windows, replace `./gradlew` with `gradlew.bat`.

## Examples

The `examples` module contains small, self-contained example programs. Each
example exists twice: once written against the current API (run with either
backend) and once against the released 2.1.2 artifact (`examples/src/legacy`).
A parity test compares the outputs byte-for-byte, so the examples double as a
behavior-comparison suite between the released version and the current
development state.

- Run every variant and compare: `./gradlew :examples:test`
- Run a single variant: `./gradlew :examples:runLegacyCreateMinimalCrate`,
`./gradlew :examples:runCurrent-jackson2CreateMinimalCrate`,
`./gradlew :examples:runCurrent-jackson3CreateMinimalCrate`
- Outputs are written to `examples/build/examples/<variant>/<example>.txt`
- List available example tasks: `./gradlew :examples:tasks --group examples`

Each variant runs in its own JVM instance, since the JSON backends register via
`META-INF/services` and must not share a classpath.

## RO-Crate Specification Compatibility

- ✅ [Version 1.1](https://www.researchobject.org/ro-crate/1.1/) ([Extracted examples as well-described unit tests/guide](src/test/java/edu/kit/datamanager/ro_crate/examples/ExamplesOfSpecificationV1p1Test.java))
- ✅ [Version 1.1](https://www.researchobject.org/ro-crate/1.1/) ([Extracted examples as well-described unit tests/guide](core/src/test/java/edu/kit/datamanager/ro_crate/examples/ExamplesOfSpecificationV1p1Test.java))
- 🛠️ Version 1.2-DRAFT
- ✅ Reading and writing crates with additional profiles or specifications ([examples for reading](src/test/java/edu/kit/datamanager/ro_crate/reader/RoCrateReaderSpec12Test.java), [examples for writing](src/test/java/edu/kit/datamanager/ro_crate/writer/RoCrateWriterSpec12Test.java))
- ✅ Adding profiles or other specifications to a crate ([examples](src/test/java/edu/kit/datamanager/ro_crate/crate/BuilderSpec12Test.java))
- ✅ Reading and writing crates with additional profiles or specifications ([examples for reading](core/src/test/java/edu/kit/datamanager/ro_crate/reader/RoCrateReaderSpec12Test.java), [examples for writing](core/src/test/java/edu/kit/datamanager/ro_crate/writer/RoCrateWriterSpec12Test.java))
- ✅ Adding profiles or other specifications to a crate ([examples](core/src/test/java/edu/kit/datamanager/ro_crate/crate/BuilderSpec12Test.java))

## Quick-start

Expand All @@ -58,7 +109,7 @@ RoCrate myFirstCrate = STARTER_CRATE

A built or imported crate can of course also be modified afterwards. Take a look at our further documentation:

- **There is a well-documented example-driven guide in [LearnByExampleTest.java](src/test/java/edu/kit/datamanager/ro_crate/examples/LearnByExampleTest.java) to help you get started.**
- You may also be interested in the examples we extracted from the [specification in version 1.1](https://www.researchobject.org/ro-crate/1.1/), which are available in [ExamplesOfSpecificationV1p1Test.java](src/test/java/edu/kit/datamanager/ro_crate/examples/ExamplesOfSpecificationV1p1Test.java).
- There is a [module with all well-described guiding tests](src/test/java/edu/kit/datamanager/ro_crate/examples/) available.
- **There is a well-documented example-driven guide in [LearnByExampleTest.java](core/src/test/java/edu/kit/datamanager/ro_crate/examples/LearnByExampleTest.java) to help you get started.**
- You may also be interested in the examples we extracted from the [specification in version 1.1](https://www.researchobject.org/ro-crate/1.1/), which are available in [ExamplesOfSpecificationV1p1Test.java](core/src/test/java/edu/kit/datamanager/ro_crate/examples/ExamplesOfSpecificationV1p1Test.java).
- There is a [module with all well-described guiding tests](core/src/test/java/edu/kit/datamanager/ro_crate/examples/) available.
- The [JavaDoc Documentation](https://javadoc.io/doc/edu.kit.datamanager/ro-crate-java) is also available online.
Loading
Loading