# Jackson Modules

When using Result objects with [**Jackson**](https://github.com/FasterXML/jackson/) we might run into some problems. The Jackson datatype modules for Result solve them by making Jackson treat results as if they were ordinary objects.

{% hint style="info" %}
[**Jackson**](https://github.com/FasterXML/jackson/) is a Java library for [JSON](https://www.json.org/) parsing and generation. It is widely used for converting Java objects to JSON and vice versa, making it essential for handling data in web services and RESTful APIs.
{% endhint %}

## How to Use These Add-Ons

Choose the Maven dependency that matches your Jackson version.

### Jackson 2.x

Add this Maven dependency to your build:

| Group ID                | Artifact ID      | Latest Version                                                                                        |
| ----------------------- | ---------------- | ----------------------------------------------------------------------------------------------------- |
| `com.leakyabstractions` | `result-jackson` | ![](https://img.shields.io/endpoint?url=https://dev.leakyabstractions.com/result-jackson/latest.json) |

### Jackson 3.x

Add this one instead:

| Group ID                | Artifact ID       | Latest Version                                                                                         |
| ----------------------- | ----------------- | ------------------------------------------------------------------------------------------------------ |
| `com.leakyabstractions` | `result-jackson3` | ![](https://img.shields.io/endpoint?url=https://dev.leakyabstractions.com/result-jackson3/latest.json) |

{% hint style="success" %}
Maven Central provides snippets for different build tools to declare these dependencies.

* [Jackson 2.x datatype module for Result](https://central.sonatype.com/artifact/com.leakyabstractions/result-jackson/)
* [Jackson 3.x datatype module for Result](https://central.sonatype.com/artifact/com.leakyabstractions/result-jackson3/)
  {% endhint %}

## Test Scenario

Let's start by creating a class `ApiResponse` containing one ordinary and one `Result` field.

```java
/** Represents an API response */
public class ApiResponse {

  @JsonProperty
  String version;

  @JsonProperty
  Result<String, String> result;

  // Constructors, getters and setters omitted
}
```

## Problem Overview

Then we will take a look at what happens when we try to serialize and deserialize `ApiResponse` objects.

### Serialization Problem (Jackson 2.x Only)

Now, let's instantiate an `ApiResponse` object.

```java
ApiResponse response = new ApiResponse();
response.setVersion("v1");
response.setResult(success("Perfect"));
```

And finally, let's try serializing it using an [object mapper](https://www.baeldung.com/jackson-object-mapper-tutorial).

```java
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(response);
```

With Jackson 2.x, this will produce an error: [`InvalidDefinitionException`](https://javadoc.io/static/com.fasterxml.jackson.core/jackson-databind/2.17.2/com/fasterxml/jackson/databind/exc/InvalidDefinitionException.html).

```
Java 8 optional type `java.util.Optional<java.lang.String>`
 not supported by default:
 add Module "com.fasterxml.jackson.datatype:jackson-datatype-jdk8"
 to enable handling
```

The reason is Jackson encounters `Optional` values internally and it will not handle it unless you register [the appropriate modules](https://github.com/FasterXML/jackson-modules-java8/).

### Deserialization Problem (Both Jackson 2.x and 3.x)

Now, let's reverse our previous example, this time trying to deserialize a JSON object into an `ApiResponse`.

```java
String json = "{\"version\":\"v2\",\"result\":{\"success\":\"OK\"}}";
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.readValue(json, ApiResponse.class);
```

This will produce an error: [`InvalidDefinitionException`](https://javadoc.io/static/com.fasterxml.jackson.core/jackson-databind/2.17.2/com/fasterxml/jackson/databind/exc/InvalidDefinitionException.html). Let's inspect the stack trace.

```
Cannot construct instance of `com.leakyabstractions.result.api.Result`
 (no Creators, like default constructor, exist):
 abstract types either need to be mapped to concrete types,
 have custom deserializer, or contain additional type information
```

This behavior again makes sense. Essentially, Jackson cannot create new result objects because `Result` is an interface, not a concrete type.

## Solution Implementation

The Jackson datatype modules for Result provide serializers and deserializers so that Jackson treats results as if they were regular objects.

### Registering the Jackson Datatype Module for Result

First of all, we need to [add the the appropriate datatype module as a dependency](#how-to-use-this-add-on).

#### Jackson 2.x

Then, all we need to do is register `ResultModule` with our [object mapper](https://www.baeldung.com/jackson-object-mapper-tutorial).

```java
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new ResultModule());
```

Alternatively, you can also make Jackson 2.x auto-discover the module.

```java
objectMapper.findAndRegisterModules();
```

#### Jackson 3.x

Just like the previous example, we need to add `ResultModule` to our [JSON mapper](https://javadoc.io/static/tools.jackson.core/jackson-databind/3.0.0/tools.jackson.databind/tools/jackson/databind/json/JsonMapper.html).

```java
JsonMapper.Builder builder = JsonMapper.builder();
builder.addModule(new ResultModule());
ObjectMapper objectMapper = builder.build();
```

Or simply use auto-discovery:

```java
builder.findAndAddModules();
```

{% hint style="info" %}
Regardless of the chosen registration mechanism, once the appropriate dataype module is registered all functionality is available for all normal Jackson operations.
{% endhint %}

### Serializing Results

Now, let's try and serialize our `ApiResponse` object again:

```java
@Test
void serializeSuccessfulResult() throws Exception {
  // Given
  ApiResponse response = new ApiResponse("v3", success("All good"));
  // When
  ObjectMapper objectMapper = new ObjectMapper();
  objectMapper.registerModule(new ResultModule());
  String json = objectMapper.writeValueAsString(response);
  // Then
  assertTrue(json.contains("v3"));
  assertTrue(json.contains("All good"));
}
```

If we look at the serialized response, we'll see that this time the `result` field contains a null `failure` value and a non-null `success` value:

```json
{
  "version": "v3",
  "result": {
    "failure": null,
    "success": "All good"
  }
}
```

Next, we can try serializing a failed result.

```java
@Test
void serializeFailedResult() throws Exception {
  // Given
  ApiResponse response = new ApiResponse("v4", failure("Oops"));
  // When
  ObjectMapper objectMapper = new ObjectMapper();
  objectMapper.findAndRegisterModules();
  String json = objectMapper.writeValueAsString(response);
  // Then
  assertTrue(json.contains("v4"));
  assertTrue(json.contains("Oops"));
} // End
```

We can verify that the serialized response contains a non-null `failure` value and a null `success` value.

```json
{
  "version": "v4",
  "result": {
    "failure": "Oops",
    "success": null
  }
}
```

### Deserializing Results

Now, let's repeat our tests for deserialization. If we read our `ApiResponse` again, we'll see that we no longer get an [`InvalidDefinitionException`](https://javadoc.io/static/com.fasterxml.jackson.core/jackson-databind/2.17.2/com/fasterxml/jackson/databind/exc/InvalidDefinitionException.html).

```java
@Test
void deserializeSuccessfulResult() {
  // Given
  String json = "{\"version\":\"v5\",\"result\":{\"success\":\"Yay\"}}";
  // When
  ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules();
  ApiResponse response = objectMapper.readValue(json, ApiResponse.class);
  // Then
  assertEquals("v5", response.getVersion());
  assertEquals("Yay", response.getResult().orElse(null));
}
```

Finally, let's repeat the test again, this time with a failed result. We'll see that yet again we don't get an exception, and in fact, have a failed result.

```java
@Test
void deserializeFailedResult() {
  // Given
  String json = "{\"version\":\"v6\",\"result\":{\"failure\":\"Nay\"}}";
  // When
  ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules();
  ApiResponse response = objectMapper.readValue(json, ApiResponse.class);
  // Then
  assertEquals("v6", response.getVersion());
  assertEquals("Nay", response.getResult().getFailure().orElse(null));
}
```

## Conclusion

We learned how to serialize and deserialize Result objects using both **Jackson 2.x** and **Jackson 2.x**, demonstrating how the provided datatype module enables Jackson to treat Results as ordinary objects.

The integration is nearly identical across versions; the main differences are limited to dependency coordinates and how the object mapper is constructed and configured.

{% hint style="success" %}
The full source code for the examples is available on GitHub.

* [Jackson 2.x examples](https://github.com/LeakyAbstractions/result-jackson/tree/main/result-jackson/src/test/java/example)
* [Jackson 3.x examples](https://github.com/LeakyAbstractions/result-jackson3/tree/main/result-jackson3/src/test/java/example)
  {% endhint %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://result.leakyabstractions.com/add-ons/jackson.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
