4

Currently deserializing with GSON and retrofit using retrofits GsonConverterFactory:

GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(new TypeToken<Map<Book, Collection<Author>>>(){}.getType(), new BooksDeserializer(context));
Gson gson = gsonBuilder.create();

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl(url)
    .addConverterFactory(GsonConverterFactory.create(gson))
    .build();

BookService service = retrofit.create(BookService.class);
Response<Map<Book, Collection<Author>>> response = service.getBooks().execute();    

I would like to use the JacksonConverterFactory that is provided by retrofit? I would need to provide that a Jackson mapper. Is there a way to provide the type information to that mapper like I did with GSON?

SimpleModule simpleModule = new SimpleModule();
// TODO provide mapper with info needed to deserialize 
// Map<Book, Collection<Author>>
mapper.registerModule(simpleModule);

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl(url)
    .addConverterFactory(JacksonConverterFactory.create(mapper))
    .build();

BookService service = retrofit.create(BookService.class);
Response<Map<Book, Collection<Author>>> response = service.getBooks().execute();

Looking specifically at the TODO can I tell the mapper to use this deserializer?

public class BooksDeserializer extends JsonDeserializer<Map<Book, Collection<Author>>> {

    @Override
    public Map<Book, Collection<Author>> deserialize(JsonParser parser, DeserializationContext context) throws IOException, JsonProcessingException {
        // deserialize here
    }
}
lostintranslation
  • 20,811
  • 42
  • 129
  • 220

2 Answers2

3

According to the API, SimpleModule.addDeserializer(java.lang.Class, com.fasterxml.jackson.databind.JsonSerializer) requires an instance of JsonSerializer<T> whereby T is a supertype of the class you supply as an argument, i.e. the deserializer needs to be able to deserialize objects which are a subclass of the supplied class; TypeReference<Map<Book, Collection<Author>>> is not a subtype of Map<Book, Collection<Author>>.

However, writing a serializer for maps is not easy thanks to Java's type erasure. One way of doing this is to write a wrapper class for your map, such as

@XmlRootElement
public class SerializableBookMapWrapper {

    private Map<Book, Collection<Author>> wrapped;

    public SerializableBookMapWrapper(final Map<Book, Collection<Author>> wrapped) {
        this.wrapped = wrapped;
    }

    public Map<Book, Collection<Author>> getWrapped() {
        return wrapped;
    }

    public void setWrapped(final Map<Book, Collection<Author>> wrapped) {
        this.wrapped = wrapped;
    }
}

With this kind of wrapper class, you can implement a JsonDeserializer<SerializableBookMapWrapper> and use that instead. However, if you haven't used Jackson annotations in the definition of Book and Author, you need to supply custom deserializers for them as well.

Alternatively, you can also try supplying this type information while actually using the ObjectMapper instance for deserialization.

Community
  • 1
  • 1
errantlinguist
  • 3,128
  • 3
  • 16
  • 34
  • Updated the question, tried to be a little more specific on what I am looking to do. – lostintranslation Feb 09 '16 at 03:44
  • *Looking specifically at the TODO can I tell the mapper to use this deserializer?* No, I'm sorry: As stated in my original answer, there is not enough information at runtime to map your deserializer to something like `Map>` because at runtime it is indistinguishable from e.g. `Map`. For that reason, you have to supply this type information either [at compile time](http://wiki.fasterxml.com/JacksonInFiveMinutes#Data_Binding_with_Generics) or [programmatically](http://stackoverflow.com/a/18014407/1391325) – errantlinguist Feb 09 '16 at 11:36
  • If that is indeed true it's not that there is not enough info, it's that Jackson does not support it. GSON is just fine handling it. I think I will stick with GSON. – lostintranslation Feb 09 '16 at 13:41
  • This is a problem I faced and solved it the same way by creating another class. – Louis F. Feb 12 '16 at 10:29
  • Out of curiosity, I looked into how [`TypeToken`](https://google-gson.googlecode.com/svn/trunk/gson/docs/javadocs/com/google/gson/reflect/TypeToken.html) works and, as expected, it does a huge amount of reflective voodoo in order to resolve types at runtime; I expect that Google have made liberal use of such black magic elsewhere in the code, which also corroborates [benchmarks which show Jackson to be more performant than Gson](http://blog.takipi.com/the-ultimate-json-library-json-simple-vs-gson-vs-jackson-vs-json/). In other words, if you want fancy type resolution, you have to pay for it. – errantlinguist Feb 20 '16 at 15:01
0

I think there is misunderstanding here: you do NOT implement JsonDeserializer just because you want to bind into some type. This is what Jackson itself does. And if you need for some reason customized deserializer (or serializer), it will only handle specific part of nested type; deserializer for Book or Author or Map, but for nested types Map deserializer delegates to key and value handlers. It does NOT register itself for combination.

Anyway: I am fairly certain that custom deserializer is not the answer here, but a way to wrap basic usage of ObjectMapper to read JSON as given type. But without knowing more about Retrofit I don't know what exactly.

StaxMan
  • 102,903
  • 28
  • 190
  • 229