简体   繁体   中英

Trying to migrate custom generic gson deserializer to jackson

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
    }
}

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 , ie 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 .

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM