3

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?

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);
Andrew Cheong
  • 29,362
  • 15
  • 90
  • 145
  • From which library `@AutoMatter` annotations comes from? Could you also post example `JSON` payload. Just to recreate given scenario. Do you use any custom configuration for `ObjectMapper`? – Michał Ziober Feb 25 '19 at 23:01
  • This one: https://github.com/danielnorberg/auto-matter – Andrew Cheong Feb 25 '19 at 23:01

2 Answers2

2

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}]]}
Michał Ziober
  • 37,175
  • 18
  • 99
  • 146
  • 1
    ...wow. Thanks so much! Might take me a bit to get this to compile. I'll definitely follow up! – Andrew Cheong Feb 25 '19 at 23:27
  • @AndrewCheong, hope that will help you. I know this is kind of opinion but some devs consider `Optional` fields in `POJO` like an [anti pattern](https://dzone.com/articles/optional-anti-patterns). – Michał Ziober Feb 25 '19 at 23:33
  • Oh, that's a great article. Yea, so it turns out we were already registering the Jdk8Module. (See edit #2.) But I'm gonna try removing all the Optionals and seeing what happens. – Andrew Cheong Feb 25 '19 at 23:36
  • @AndrewCheong, I noticed your changes. Thats strange. You need to have a really complex `JSON` that probably `Jackson` can not resolve to a correct class. My answer does not provide anything new for you. Removing `Optional`s probably will not solve an error. – Michał Ziober Feb 25 '19 at 23:40
  • 1
    Well you've still shed light that the internal structure of the deserializing class actually matters. I had no idea about that. After removing Optionals, I'm gonna start simplifying my models to see at what point it starts working again, if any. – Andrew Cheong Feb 25 '19 at 23:44
  • @AndrewCheong, just a guess but do you use maybe `Java 9+`? I am asking because I was trying to create very complex structures and it was working on `Java 8`. Maybe there is a strange class loader problem when we are working with [Java 9 modularity](https://www.baeldung.com/java-9-modularity). It is definitely worth to check in case you are using `Java 9+`. – Michał Ziober Feb 26 '19 at 00:35
  • @AndrewCheong, also I forget to ask which version of `Jackson` do you use? In case this is not the newest one try to upgrade it because I checked it for the newest `2.9.8`. – Michał Ziober Feb 26 '19 at 00:41
  • I'm actually on Java 11. And I was on Jackson 2.9.7. Not sure it's worth me reverting my code to see if 2.9.8 works, but it seems the culprit would more likely be Java 11. – Andrew Cheong Feb 26 '19 at 02:57
0

For now I just had to abandon parameterizing. I only had so many classes,

Foo<A>
Foo<B>
Foo<C>

It was easier to just create each one explicitly:

@AutoMatter
public interface FooA {
  A parent;
  List<A> children;
}

@AutoMatter
public interface FooB {
  B parent;
  List<B> children;
}

@AutoMatter
public interface FooC {
  C parent;
  List<C> children;
}

(I've also realized the Optionals were unnecessary.)

Not really an answer, so not accepting it.

Andrew Cheong
  • 29,362
  • 15
  • 90
  • 145