Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ClassCastException when deserializing with ObjectMapper onto parametrized class, even using TypeReference and TypeFactory

I'm getting the following exception when trying to deserialize with ObjectMapper onto a parameterized class (works fine for non-parametrized classes):

java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to class com.xyz.A (java.util.LinkedHashMap is in module java.base of loader 'bootstrap'; com.xyz.A is in unnamed module of loader 'app')

Here's the original code:

Foo<A> request = OBJECT_MAPPER.readValue(payload, Foo.class);

I tried:

Foo<A> request = OBJECT_MAPPER.readValue(payload, new TypeReference<Foo<A>>() {});

As well as:

JavaType myType = OBJECT_MAPPER.getTypeFactory()
    .constructParametricType(Foo.class, A.class);
Foo<A> request = OBJECT_MAPPER.readValue(payload, myType);

But I still get the same exception.

Could there be something special about my scenario that's not covered in these questions?

  • Jackson is not deserialising a generic list that it has serialised
  • Jackson and generic type reference
  • Jackson - Deserialize using generic class

One thing I can think of is that my Foo is actually an @AutoMatter-annotated interface that generates the class:

@AutoMatter
public interface Foo<T> {
  Optional<T> parent;
  Optional<List<T>> children;
}

Normally we have no issues mapping onto AutoMatter-generated classes though. It's just adding the parametrization <T> that seems to be causing issues.

Does anyone have an idea?


Edit to answer @MichalZiober's questions:

In my test code I'm actually just serializing what I know is a valid object, i.e. then deserializing that to get back the object I started with:

Foo<A> myExampleObject;
ByteString.encodeUtf8(OBJECT_MAPPER.writeValueAsString(myExampleObject));

Edit 2

Okay, so it looks like we are already importing that module:

  @VisibleForTesting
  public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
      .registerModule(new JodaModule())
      .registerModule(new GuavaModule())
      .registerModule(new AutoMatterModule())
      .registerModule(new Jdk8Module())
      .registerModule(new ProtobufModule())
      .setSerializationInclusion(JsonInclude.Include.NON_NULL)
      .configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
      .configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false)
      .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
      .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
like image 882
slackwing Avatar asked Dec 08 '25 10:12

slackwing


1 Answers

When you use Optional in POJO structure you need to enable Jdk8Module from jackson-modules-java8. Below example shows that with this module registered we can serialise and deserialise data:

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import io.norberg.automatter.AutoMatter;
import io.norberg.automatter.jackson.AutoMatterModule;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

public class JsonApp {

    public static void main(String[] args) throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        mapper.enable(SerializationFeature.INDENT_OUTPUT);
        mapper.registerModule(new AutoMatterModule());
        mapper.registerModule(new Jdk8Module());

        String json = "{\"parent\":\"Aaaa\", \"children\":[\"a\"]}";
        System.out.println(mapper.readValue(json, Foo.class));


        Foo<StringWrapper> foo = new FooBuilder<StringWrapper>()
                .parent(new StringWrapperBuilder().value("PARENT").build())
                .children(Arrays.asList(new StringWrapperBuilder().value("CHILD1").build()))
                .build();
        json = mapper.writeValueAsString(foo);
        System.out.println(json);
        System.out.println(mapper.readValue(json, Foo.class));
    }
}
@AutoMatter
interface Foo<T> {
    Optional<T> parent();
    Optional<List<T>> children();
}

@AutoMatter
interface StringWrapper {
    String value();
}

Above code prints:

Foo{parent=Optional[Aaaa], children=Optional[[a]]}
{
  "parent" : {
    "value" : "PARENT"
  },
  "children" : [ {
    "value" : "CHILD1"
  } ]
}
Foo{parent=Optional[{value=PARENT}], children=Optional[[{value=CHILD1}]]}
like image 194
Michał Ziober Avatar answered Dec 10 '25 00:12

Michał Ziober