Search code examples
jsongroovyjacksonjavers

Jackson does not convert nested Java 8 Optional from Javers Change object, what is special about it?


I am trying to send a list of Javers Changes as JSON via a REST api. While Jackson can handle Java 8 Optionals via loading the respective module it fails to serialize the Change object. When i create a class with Optionals myself the serialization works as expected.

To reproduce one can run the following groovy script:

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module
import org.javers.core.JaversBuilder
import org.javers.repository.jql.QueryBuilder

@Grapes([
  @Grab("com.fasterxml.jackson.core:jackson-core:2.8.3"),
  @Grab(group='com.fasterxml.jackson.datatype', module='jackson-datatype-jdk8', version='2.8.3'),
  @Grab('org.javers:javers-core:2.3.0')]
)

class Test {
  def bar
  def baz

  Test(){
    baz = "baz"
    bar = "bar"
  }
}

def test = new Test()

def javers = JaversBuilder.javers().build()

javers.commit("user", test)

test.bar = "foo"

javers.commit("user", test)

def objectMapper = new ObjectMapper()

objectMapper.registerModule(new Jdk8Module())

println objectMapper.writeValueAsString(javers.findChanges(QueryBuilder.anyDomainObject().build()))

This outputs:

[
  {
    "commitMetadata": {
      "empty": false,
      "present": true
    },
    "propertyName": "bar",
    "left": "bar",
    "right": "foo",
    "affectedGlobalId": {
      "typeName": "Test"
    },
    "affectedLocalId": null,
    "affectedObject": {
      "empty": true,
      "present": false
    }
  }
]

A custom class gets serialzed as expected:

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module

@Grapes([
        @Grab("com.fasterxml.jackson.core:jackson-core:2.8.3"),
        @Grab(group='com.fasterxml.jackson.datatype', module='jackson-datatype-jdk8', version='2.8.3')]
)

class Test2 {
  def bar
  def baz
  Test2(){
    baz = Optional.of("baz")
    bar = "bar"
  }
}

def test = new Test2()

def objectMapper = new ObjectMapper()

objectMapper.registerModule(new Jdk8Module())

println objectMapper.writeValueAsString(test)

Output:

{
  "bar": "bar",
  "baz": "baz"
}

What is special about the Javers Change class, that Jackson refuses to serialze the Optionals?


Solution

  • Jackson doesn't know Javers' Optionals and tries to serialize them as a standard Java Bean which is wrong in this case. You can fix it by writing a custom serializer for Jackson.

    Javers' Optionals exists because replacing them with Java8 Optionals would brake Java7 clients.