Serializing / Deserializing Map / List with spin & jackson

Hi,

We are migrating from java serialization to json serialization with spin & jackson.
I am having some issues figuring out how to serialize & deserialize Map / List variables that contain other Dto objects.

For example:

Map<String, TestDto> map = new HashMap<>();
map.put("testDto", testDto);

runtimeService.setVariable(process.getProcessInstanceId(), "map", map);

Map<String, TestDto> mapAgain = (Map<String, TestDto>) runtimeService.getVariableTyped(process.getProcessInstanceId(), "map").getValue();

This results in:

java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to class com.someCompany.TestDto

This is because the type cannot be resolved. The serialized value looks like:

{"testDto": {"someField": "someValue"}}

So what happens is on deserialize the TestDto got deserialized into a LinkedHashMap because the TestDto type got lost. So you end up with a LinkedHashMap<String, LinkedHashMap> instead of Map<String, TestDto>. (LinkedHashMap is probably some jackson fallback default, if I remember correctly)

After lots of google searching, I came to the conclusion that I need to configure the jackson objectmapper used by camunda. (see the reply from thorben in this thread How to properly de-/serialize Collections to JSON - #5 by stefanzilske)

Thus I came up with this config, following the documentation:

public class CamundaJacksonConfiguration implements DataFormatConfigurator<JacksonJsonDataFormat> {

    public void configure(JacksonJsonDataFormat dataFormat) {
        dataFormat.getObjectMapper()
                .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
                .activateDefaultTyping(
                        new LaissezFaireSubTypeValidator(),
                        ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE,
                        JsonTypeInfo.As.PROPERTY
                );
    }

    public Class<JacksonJsonDataFormat> getDataFormatClass() {
        return JacksonJsonDataFormat.class;
    }
}

This makes my test work for both map and list. Though I had to add the jackson FAIL_ON_UNKNOWN_PROPERTIES, otherwise the list deserialization kept failing because it wants to map the type property ‘@class’ but it doesn’t exist on the dto. So it seems it is ignoring the jackson typing?

This is the only configuration that I found that makes the test pass. Changing the JsonTypeInfo.As.?, DefaultTyping.? setting to something else always ends up in serialization / deserialization errors on either the map or the list. (errors SPIN/JACKSON-JSON-01006 / SPIN/JACKSON-JSON-01009)

Also, this solution is still not usable for me because now it brakes things in other places, where we have a list variable that contains a simple map, e.g.: List<Map<String, String>
After deserializing we get the correct structure but the map contains an extra entry:
key: “@class”, value: “java.util.HashMap”

What are the options here?

Also, further info, we are using camunda with spring-boot and the following spin dependencies:

    <dependency>
        <groupId>org.camunda.spin</groupId>
        <artifactId>camunda-spin-dataformat-json-jackson</artifactId>
        <version>1.13.0</version>
    </dependency>
    <dependency>
        <groupId>org.camunda.bpm</groupId>
        <artifactId>camunda-engine-plugin-spin</artifactId>
    </dependency>

So no xml or dataformat-all dependencies.

This can be fixed by using the following configuration:

public class SpinTypeDetectorConfigurator implements DataFormatConfigurator<JacksonJsonDataFormat> {

  @Override
  public Class<JacksonJsonDataFormat> getDataFormatClass() {
    return JacksonJsonDataFormat.class;
  }

  @Override
  public void configure(JacksonJsonDataFormat dataFormat) {
    dataFormat.addTypeDetector(MapJacksonJsonTypeDetector.INSTANCE);
  }
}

However, there is bug with this solution. An exception is thrown when the map / list contains a null value. (see: https://jira.camunda.com/browse/CAM-14331)