devonfw / devon4quarkus

Apache License 2.0
1 stars 9 forks source link

Evaluate custom JSON serialization #28

Closed GuentherJulian closed 3 years ago

GuentherJulian commented 3 years ago

At the moment we use PageImpl as return type in our Rest Services. If we only use the Page interface, serialization will not work in native mode. This is because JSON serialization uses reflection to determine the properties of an object. When using Page, only the interface is registered for reflection by Quarkus, so the other properties needed for paging are not accessible in native mode.

In devon4j there were also issues with serializing Page to/from JSON. The solution in devon4j was to configure Jackson accordingly. https://github.com/devonfw/devon4j/tree/master/modules/json/src/main/java/com/devonfw/module/json/common/base/type

We need to check if there is also a solution for Quarkus so that we can use Page instead of PageImpl.

lilliCao commented 3 years ago

I tested the json-module.

    <dependency>
      <groupId>com.devonfw.java.modules</groupId>
      <artifactId>devon4j-json</artifactId>
      <version>2021.04.003</version>
    </dependency>
@Named("ApplicationObjectMapperFactory")
public class ApplicationObjectMapperFactory extends ObjectMapperFactory {
    public ApplicationObjectMapperFactory() {

        super();
        SimpleModule module = getExtensionModule();
        // register spring-data Pageable
        module.addSerializer(Pageable.class, new PageableJsonSerializer());
        module.addDeserializer(Pageable.class, new PageableJsonDeserializer());
    }
}

and inject custom mapper to resteasy

@Component
@Provider
@Consumes({"application/json", "application/*+json", "text/json"})
@Produces({"application/json", "application/*+json", "text/json"})
public class CustomJacksonJsonProvider extends ResteasyJackson2Provider {

    @Inject
    ObjectMapperFactory objectMapperFactory;

    @Override
    public ObjectMapper locateMapper(Class<?> type, MediaType mediaType) {
        ObjectMapper om = objectMapperFactory.createInstance();
        return om;
    }
}

Result:

GuentherJulian commented 3 years ago

I think you do not simple can use the devon4j-json module in the Quarkus context. You have to implement the ObjectMapperCustomizer (io.quarkus.jackson.ObjectMapperCustomizer) as described here. Then you can add a custom serializer in the customize method.

@Override
public void customize(ObjectMapper objectMapper) {
    SimpleModule module = new SimpleModule();
    module.addSerializer(Page.class, new JsonSerializer<Page>() {

        @Override
    public void serialize(Page value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
            ...
        }
    });
    objectMapper.registerModule(module);
}
lilliCao commented 3 years ago

@GuentherJulian you are right. That should do it. Thanks. I saw the problem now, even the json-module does not provide a serializer for Page.class (only Pagable.class)

lilliCao commented 3 years ago

Implement custom mapper using io.quarkus.jackson.ObjectMapperCustomizer (code in this branch)

the custom serializer for Page.class. We can customize further here if wanted

public class PageSerializer extends JsonSerializer<Page> {

    @Override
    public void serialize(Page page, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        if (page != null) {
            jsonGenerator.writeStartObject();

            jsonGenerator.writeObjectField("content", page.getContent());
            jsonGenerator.writeObjectField("pageable", page.getPageable());
            jsonGenerator.writeBooleanField("last", page.isLast());
            jsonGenerator.writeNumberField("totalPages", page.getTotalPages());
            jsonGenerator.writeNumberField("totalElements", page.getTotalElements());
            jsonGenerator.writeBooleanField("first", page.isFirst());
            jsonGenerator.writeNumberField("numberOfElements", page.getNumberOfElements());
            jsonGenerator.writeObjectField("sort", page.getSort());
            jsonGenerator.writeNumberField("number", page.getNumber());
            jsonGenerator.writeNumberField("size", page.getSize());
            jsonGenerator.writeBooleanField("empty", page.isEmpty());

            jsonGenerator.writeEndObject();
        }
    }
}

register custom serializer to object mapper

@Singleton
public class CustomObjectMapper implements ObjectMapperCustomizer {

    @Override
    public void customize(ObjectMapper objectMapper) {
        SimpleModule module = new SimpleModule();
        module.addSerializer(Page.class, new PageSerializer());
        objectMapper.registerModule(module);
    }
}

Result:

@GuentherJulian should this be sufficient? We can create another issue to refactor the sample app.

GuentherJulian commented 3 years ago

@GuentherJulian should this be sufficient? We can create another issue to refactor the sample app.

Great! Yes, please create a following task to refactor the rest service.

lilliCao commented 3 years ago

Refactoring the sample app should follow by this issue

sobkowiak commented 3 years ago

It looks ok for me