0

I am using a Spring Boot application (Version 2.4.5) with Jackson. Can I add an fat jar which is using gson without affecting the Jackson deserialisation of the application?

When I add the jar to my application, the application uses gson everywhere to deserialise JSONs. But since I am using some special functions of Jackson this fails.

I can unfortunately not change this in the library.

I use Gradle to add the fat jar like this in the dependencies:

implementation fileTree(dir: 'libs', include: ['*.jar'])

I am building Jacksons object mapper like:

private static ObjectMapper getMapper() {
  ObjectMapper mapper = new ObjectMapper();
  mapper.registerModule(new Jdk8Module());
  mapper.registerModule(new JavaTimeModule());

  return mapper;
}

And following lines throws an error:

List<User> users = objectMapper.readValue(getReader("users.json"), new TypeReference<>() {});
java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to class User (java.util.LinkedHashMap is in module java.base of loader 'bootstrap'; onek.books.backend.model.book.Book is in unnamed module of loader org.springframework.boot.devtools.restart.classloader.RestartClassLoader @445d3853)

And Gson is added to fat jar with following maven dependencies:

<dependency>
  <groupId>com.google.code.gson</groupId>
  <artifactId>gson</artifactId>
  <version>${gson-version}</version>
</dependency>
<dependency>
  <groupId>io.gsonfire</groupId>
  <artifactId>gson-fire</artifactId>
  <version>${gson-fire-version}</version>
</dependency>

Edit

As suggested that is not due to gson I tried to narrow it down and added all dependecies one by one and none caused the error by itself.

But I might have found the cause, but I don't know how to solve this. It is the way Jacksons typeFactory construct it's type. For the line:

List<Book> books = objectMapper.readValue(getReader("books.json"), new TypeReference<>() {});

If the jar ist not existent the constructed Type looks like:

result = {CollectionType@13667} "[collection type; class java.util.List, contains [simple type, class onek.books.backend.model.book.Book]]"
 _elementType = {SimpleType@13669} "[simple type, class onek.books.backend.model.book.Book]"
 _superClass = null
 _superInterfaces = {JavaType[1]@13670} 
 _bindings = {TypeBindings@13671} "<Lonek/books/backend/model/book/Book;>"
 _canonicalName = null
 _class = {Class@352} "interface java.util.List"
 _hash = -95724352
 _valueHandler = null
 _typeHandler = null
 _asStatic = false

Which is what I would expect because the Book-Class is in the type. But if I add the jar the constructed Type looks like:

result = {SimpleType@11757} "[simple type, class java.lang.Object]"
 _superClass = null
 _superInterfaces = null
 _bindings = {TypeBindings@11759} "<>"
 _canonicalName = null
 _class = {Class@595} "class java.lang.Object"
 _hash = 1063877011
 _valueHandler = null
 _typeHandler = null
 _asStatic = false

And jackson deserialize it as an LinkedHashmap with this type causing the ClassCastException.

But I realy don't know how to continue from here. Can an external jar alter some parts of the Jackson TypeFactory?

Febell
  • 43
  • 5
  • see https://github.com/google/gson/issues/1875 – Joaquín L. Robles Nov 26 '21 at 18:25
  • Based on the code snippet you provided, it is not clear to me what this has to do with Gson. You are explicitly calling `readValue` of Jackson's `ObjectMapper`; Gson should have no effect on this. – Marcono1234 Nov 26 '21 at 20:44
  • Yeah I thought so myself. But it is pretty clear that this jar with gson in it causes the trouble since the line works without it but not with it. – Febell Nov 28 '21 at 10:26
  • @Marcono1234 you are totaly right. The dependecy does nothing (see my edit). But somehow the jar alter the result of the type construction. – Febell Nov 28 '21 at 13:18

2 Answers2

0

When Jackson tries deserializing an object for which no type information is provided, it uses a LinkedHashMap to store the object properties values. The exception can be related to that.

My guess is that probably that fat jar includes a different version of Jackson from the one you included in your project and in which for some reason this behavior is different.

I do not know if it is just an inaccuracy in the question, but in order to solve the problem try providing the actual types in your TypeReference:

List<Book> books = objectMapper.readValue(getReader("books.json"), new TypeReference<List<Book>>() {});

Please, consider read this related article and SO question.

jccampanero
  • 50,989
  • 3
  • 20
  • 49
  • Thx, that is exactly what was happening. There were an older version of Jackson in the jars dependecies. I managed to build the jar with the same version as the server and it works now. I was not able to change the TypeReference like you suggested tho. This just throws other errors. – Febell Dec 03 '21 at 09:38
0

Most likely the issue lies within the creation of the "fat JAR".

When creating the fat JAR pay attention to the following things:

  • META-INF/MANIFEST.MF provided by dependencies should be ignored
  • module-info.class and META-INF/versions/<version>/module-info.class provided by dependencies should be ignored (the latter is used by Multi-Release JARs)
  • The content of files under META-INF/services/ should be merged (they declare providers for services)
  • Optional: If one of the dependencies is a Multi-Release JARs you can set Multi-Release: true in the MANIFEST.MF of your fat JAR to allow the JRE to use these version specific classes
  • For all other files it might be application specific how to handle conflicts

In case you are using a plugin to generate the fat JAR, then it might offer settings for these transformations (or might perform them by default).

Marcono1234
  • 5,856
  • 1
  • 25
  • 43