title | description | image |
---|---|---|
Jackson Datatype Module for Result |
Result-Jackson provides a Jackson datatype module for Result objects |
When using Result objects with Jackson we might run into some problems. The Jackson datatype module for Result solves them by making Jackson treat results as if they were ordinary objects.
Jackson is a Java library for JSON 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.
Add this Maven dependency to your build:
- Group ID:
com.leakyabstractions
- Artifact ID:
result-jackson
- Version:
{{ site.current_version }}
Maven Central provides snippets for different build tools to declare this dependency.
Let's start by creating a class ApiResponse
containing one ordinary and one Result
field.
{% include_relative result-jackson/src/test/java/example/ApiResponse.java %}
Then we will take a look at what happens when we try to serialize and deserialize ApiResponse
objects.
Now, let's instantiate an ApiResponse
object.
{% include_relative result-jackson/src/test/java/example/Fragments.java fragment="instantiate" %}
And finally, let's try serializing it using an object mapper.
{% include_relative result-jackson/src/test/java/example/Fragments.java fragment="serialize" %}
We'll see that now we get an InvalidDefinitionException
.
{% include_relative result-jackson/src/test/resources/serialization_error.txt %}
While this may look strange, it's the expected behavior. When Jackson examined the result object, it invoked
Result::getSuccess
and received an optional string value. But Jackson will not handle JDK 8
datatypes like Optional
unless you register the appropriate modules.
{% include_relative result-jackson/src/test/java/example/Example_Test.java test="serialization_problem" %}
This is Jackson's default serialization behavior. But we'd like to serialize the result
field like this:
{% include_relative result-jackson/src/test/resources/expected_serialized_result.json %}
Now, let's reverse our previous example, this time trying to deserialize a JSON object into an ApiResponse
.
{% include_relative result-jackson/src/test/java/example/Fragments.java fragment="deserialize" %}
We'll see that we get another InvalidDefinitionException
. Let's inspect the stack trace.
{% include_relative result-jackson/src/test/resources/deserialization_error.txt %}
This behavior again makes sense. Essentially, Jackson cannot create new result objects because Result
is an interface,
not a concrete type.
{% include_relative result-jackson/src/test/java/example/Example_Test.java test="deserialization_problem" %}
What we want, is for Jackson to treat Result
values as JSON objects that contain either a success
or a failure
value. Fortunately, there's a Jackson module that can solve this problem.
Once we have added Result-Jackson as a dependency, all we need to do is register ResultModule
with our object mapper.
{% include_relative result-jackson/src/test/java/example/Fragments.java fragment="register_manually" %}
Alternatively, you can also make Jackson auto-discover the module.
{% include_relative result-jackson/src/test/java/example/Fragments.java fragment="register_automatically" %}
Regardless of the chosen registration mechanism, once the module is registered all functionality is available for all normal Jackson operations.
Now, let's try and serialize our ApiResponse
object again:
{% include_relative result-jackson/src/test/java/example/Example_Test.java test="serialize_successful_result" %}
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:
{% include_relative result-jackson/src/test/resources/serialization_solution_successful_result.json %}
Next, we can try serializing a failed result.
{% include_relative result-jackson/src/test/java/example/Example_Test.java test="serialize_failed_result" %}
We can verify that the serialized response contains a non-null failure
value and a null success
value.
{% include_relative result-jackson/src/test/resources/serialization_solution_failed_result.json %}
Now, let's repeat our tests for deserialization. If we read our ApiResponse
again, we'll see that we no longer get an
InvalidDefinitionException
.
{% include_relative result-jackson/src/test/java/example/Example_Test.java test="deserialize_successful_result" %}
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.
{% include_relative result-jackson/src/test/java/example/Example_Test.java test="deserialize_failed_result" %}
You have learned how to use results with Jackson without any problems by leveraging the Jackson datatype module for Result, demonstrating how it enables Jackson to treat Result objects as ordinary fields.
The full source code for the examples is available on GitHub.