Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 96 additions & 26 deletions docs/guides/dbjson-mapping-support.md
Original file line number Diff line number Diff line change
@@ -1,40 +1,39 @@
# Guide: `@DbJson` / `@DbJsonB` mapping support — built-in vs Jackson ObjectMapper
# Guide: `@DbJson` / `@DbJsonB` mapping support

## Purpose

Ebean can map `@DbJson` and `@DbJsonB` properties in two ways:
Ebean can map `@DbJson` and `@DbJsonB` properties in three ways:

- **Built-in** JSON support, backed by **avaje-json-core** — no extra dependency.
- **Jackson `ObjectMapper`**, provided by the **`ebean-jackson-mapper`** module — used
for everything the built-in support does not handle.
- **Jackson `ObjectMapper`**, provided by **`ebean-jackson-mapper`**.
- **Avaje Jsonb**, provided by **`ebean-avajejsonb-mapper`** and its generated adapters.

This guide lists exactly which property types are handled built-in and which require
`ebean-jackson-mapper`.
The built-in mapper handles the common natural JSON types. Add one mapper module for
typed collections, POJOs, records, and other custom types.

> If a property type is **not** handled built-in and `ebean-jackson-mapper` is not on the
> classpath, Ebean fails fast at startup:
> If a property type is **not** handled built-in and no mapper module is on the classpath,
> Ebean fails fast at startup:
>
> ```text
> Unsupported @DbJson mapping - Missing dependency ebean-jackson-mapper?
> Jackson ObjectMapper not present for <property>
> Unsupported @DbJson mapping - missing JSON mapper dependency for <property>
> ```

---

## Quick reference

| Property type | Built-in (avaje-json-core) | Needs `ebean-jackson-mapper` |
|---|:---:|:---:|
| Property type | Built-in (avaje-json-core) | Mapper module |
|---|:---:|---|
| `String` | ✅ | |
| `List<String>`, `List<Long>` | ✅ | |
| `Set<String>`, `Set<Long>` | ✅ | |
| `Map<String, Object>`, `Map<String, ?>` | ✅ | |
| `Map<String, String>` | ✅ | |
| `Map<Enum, Object>`, `Map<Enum, String>` | ✅ | |
| `List`/`Set` of any other element type (`Integer`, `Double`, `UUID`, `LocalDate`, an enum, a POJO, …) | | |
| `Map` with a typed value other than `String`/`Object` (`Map<String,Integer>`, `Map<String,UUID>`, …) | | |
| `Map` with a key other than `String` or an enum (`Map<Integer, …>`, `Map<UUID, …>`) | | |
| POJOs, records, or any other type | | |
| `List`/`Set` of any other element type (`Integer`, `Double`, `UUID`, `LocalDate`, an enum, a POJO, …) | | Jackson or Avaje Jsonb |
| `Map` with a typed value other than `String`/`Object` (`Map<String,Integer>`, `Map<String,UUID>`, …) | | Jackson or Avaje Jsonb |
| `Map` with a key other than `String` or an enum (`Map<Integer, …>`, `Map<UUID, …>`) | | Jackson or Avaje Jsonb |
| POJOs, records, or any other type | | Jackson or Avaje Jsonb |

---

Expand All @@ -59,10 +58,10 @@ Postgres `json` / `jsonb` — without `ebean-jackson-mapper`.

---

## Everything else → Jackson `ObjectMapper`
## Jackson `ObjectMapper`

Any other `@DbJson` / `@DbJsonB` property routes to the Jackson `ObjectMapper` path, which
requires `ebean-jackson-mapper`:
`ebean-jackson-mapper` uses a Jackson `ObjectMapper` for the property types not handled
built-in:

- **Typed collections** — `List`/`Set` whose element type is not `String` or `Long`
(for example `List<Integer>`, `List<UUID>`, `List<LocalDate>`, `List<MyEnum>`, `List<MyPojo>`).
Expand All @@ -77,7 +76,7 @@ requires `ebean-jackson-mapper`:

---

## Adding `ebean-jackson-mapper`
### Adding `ebean-jackson-mapper`

```xml
<dependency>
Expand All @@ -92,6 +91,79 @@ registers the mapper-based JSON support automatically.

---

## Avaje Jsonb

`ebean-avajejsonb-mapper` uses Avaje Jsonb adapters. Annotate each JSON payload type with
`@Json`, or use `@Json.Import`, and configure `avaje-jsonb-generator` as an annotation
processor.

```xml
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-avajejsonb-mapper</artifactId>
<version>${ebean.version}</version>
</dependency>
```

```xml
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.avaje</groupId>
<artifactId>avaje-jsonb-generator</artifactId>
<version>${avaje-jsonb.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
```

For example:

```java
import io.avaje.jsonb.Json;

@Json
public class Address {
public String line1;
public String city;
}
```

Avaje Jsonb preserves a property's declared generic type, so `List<Address>` and other
parameterised JSON values use the generated `Address` adapter.

### Avaje JsonNode

`@DbJson` and `@DbJsonB` properties declared as `io.avaje.json.node.JsonNode` are supported
when the application includes `avaje-json-node`. Its Jsonb component supplies the JSON tree
adapters; no application-generated adapter is needed for the node hierarchy.

```xml
<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-json-node</artifactId>
<version>${avaje-jsonb.version}</version>
</dependency>
```

## Mapper module selection

Use exactly one mapper module in an application: `ebean-jackson-mapper` or
`ebean-avajejsonb-mapper`. Ebean selects a single `ScalarJsonMapper` service provider, so
having both modules on the runtime classpath is not supported.

The `ebean` composite dependency includes `ebean-jackson-mapper`. Applications using Avaje
Jsonb should depend on the individual Ebean modules they need instead of that composite.

To migrate from Jackson to Avaje Jsonb, remove `ebean-jackson-mapper`, add
`ebean-avajejsonb-mapper`, and generate adapters for each JSON payload type.

---

## Notes

- **Enum map keys** are serialised using the enum `name()` (for example `ACTIVE`), not any
Expand All @@ -102,15 +174,13 @@ registers the mapper-based JSON support automatically.
(with a JSON fallback on platforms without array support) and supports more element types
than built-in `@DbJson` collections.
- The reason typed value/element collections need a real mapper is that the built-in path
only produces natural JSON types — for example a JSON number always parses to `Long`, so a
declared `List<Integer>` or `Map<String,Integer>` could not be populated safely without a
type-aware mapper.
only produces natural JSON types — for example a JSON number always parses to `Long`.

---

## Choosing

- Prefer the **built-in** mappings for the common cases (`String`, string/long lists and sets,
object/string maps) to avoid pulling in Jackson.
- Add **`ebean-jackson-mapper`** when you need rich POJO JSON columns or typed collections /
typed-value maps.
object/string maps) to avoid an additional mapper module.
- Add **`ebean-jackson-mapper`** or **`ebean-avajejsonb-mapper`** when you need rich POJO JSON
columns or typed collections / typed-value maps.
113 changes: 113 additions & 0 deletions ebean-avajejsonb-mapper/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.3.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>ebean-avajejsonb-mapper</artifactId>
<name>ebean-avajejsonb-mapper</name>

<dependencies>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>18.3.0</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-json-core</artifactId>
<version>${avaje-json-core.version}</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-jsonb</artifactId>
<version>${avaje-jsonb.version}</version>
</dependency>

<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-json-node</artifactId>
<version>${avaje-jsonb.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>18.3.0</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-h2</artifactId>
<version>18.3.0</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-datasource</artifactId>
<version>${ebean-datasource.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>${h2database.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>18.3.0</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.avaje</groupId>
<artifactId>avaje-jsonb-generator</artifactId>
<version>${avaje-jsonb.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>

<plugin>
<groupId>io.ebean</groupId>
<artifactId>ebean-maven-plugin</artifactId>
<version>${ebean-maven-plugin.version}</version>
<executions>
<execution>
<id>test</id>
<phase>process-test-classes</phase>
<configuration>
<packages>org/example/avajejsonb/**</packages>
<transformArgs>debug=0</transformArgs>
</configuration>
<goals>
<goal>testEnhance</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Loading
Loading