Why is my custom json serializer for Spring data's Page object not being called

Jazzepi :

Here's my object mapper configuration. I provide my own object mapper so that Spring boot's 2.0's included object mapper is overridden. Despite that I also included the JsonComponentModule() so that I could use the @JsonComponent annotation to pickup custom serializers.

public class ObjectMapperConfigurer {
    public static ObjectMapper configureObjectMapper(ObjectMapper objectMapper) {
        return objectMapper.registerModules(
                // First three modules can be found here. https://github.com/FasterXML/jackson-modules-java8
                new Jdk8Module(), // support for other new Java 8 datatypes outside of date/time: most notably Optional, OptionalLong, OptionalDouble
                new JavaTimeModule(), // support for Java 8 date/time types (specified in JSR-310 specification)
                new ParameterNamesModule(), // Support for detecting constructor and factory method ("creator") parameters without having to use @JsonProperty annotation
                // These two modules are provided by spring
                new JsonComponentModule(), // Enables https://docs.spring.io/spring-boot/docs/2.1.0.RELEASE/reference/htmlsingle/#boot-features-json-components
                new GeoModule(), // Enables marshalling of GeoResult<T>, GeoResults<T>, and GeoPage<T>
                new Hibernate5Module().enable(Hibernate5Module.Feature.FORCE_LAZY_LOADING) // Allows jackson to gracefully handle Hibernate lazy loading,
        )
                .setSerializationInclusion(JsonInclude.Include.NON_NULL)
                // Turn on/off some features. https://github.com/FasterXML/jackson-databind/wiki/JacksonFeatures
                .enable(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)
                .enable(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY)
                .enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS)
                .enable(SerializationFeature.INDENT_OUTPUT)
                .enable(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS)
                .disable(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS)
                .disable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS)
                .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
                .disable(MapperFeature.DEFAULT_VIEW_INCLUSION)
                .setFilterProvider(new SimpleFilterProvider().addFilter(FilteringAnnotationInspector.DEFAULT_FILTER, new DepthFilter()))
                .setAnnotationIntrospector(new FilteringAnnotationInspector());
    }

    public static ObjectMapper configureObjectMapper() {
        return configureObjectMapper(new ObjectMapper());
    }

}

Here's my custom serializer. It's okay if the implementation of the serialization is wrong, my core problem is that it's not getting invoked.

@JsonComponent
public class PageJsonSerializer extends JsonSerializer<Page> {

    @Override
    public void serialize(Page page, JsonGenerator jsonGen, SerializerProvider serializerProvider) throws IOException {
        System.out.println("serializing using pagejsonserializer");
        ObjectMapper om = new ObjectMapper()
                .disable(MapperFeature.DEFAULT_VIEW_INCLUSION)
                .setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
        jsonGen.writeStartObject();
        jsonGen.writeFieldName("size");
        jsonGen.writeNumber(page.getSize());
        jsonGen.writeFieldName("number");
        jsonGen.writeNumber(page.getNumber());
        jsonGen.writeFieldName("totalElements");
        jsonGen.writeNumber(page.getTotalElements());
        jsonGen.writeFieldName("last");
        jsonGen.writeBoolean(page.isLast());
        jsonGen.writeFieldName("totalPages");
        jsonGen.writeNumber(page.getTotalPages());
        jsonGen.writeObjectField("sort", page.getSort());
        jsonGen.writeFieldName("first");
        jsonGen.writeBoolean(page.isFirst());
        jsonGen.writeFieldName("numberOfElements");
        jsonGen.writeNumber(page.getNumberOfElements());
        jsonGen.writeFieldName("content");
        jsonGen.writeRawValue(om.writerWithView(serializerProvider.getActiveView()).writeValueAsString(page.getContent()));
        jsonGen.writeEndObject();
    }
}

Here's the controller returning the Page(d) object.

@GetMapping(JobController.uri.path)
@PreAuthorize("hasAuthority('" + SpringSecurityConfig.Authority.LIST_JOBS + "')")
public Page<Job> listJobs(Pageable pageable) {
  return jobRepository.findAllByCandidates(currentUserHolder.getUser(), pageable);
}

I expect the above to lead to the calling the PageJsonSerializer class. I can see that class getting registered successfully with the JsonComponentModule(). I've also tried many different variations of the class declaration. Like the following but none of them have had any effect.

public class PageJsonSerializer extends JsonSerializer<Page<?>> {
public class PageJsonSerializer extends JsonSerializer<PageImpl> {

I'm not sure what to check next. I'm also struggling to see where Jackson lines up the object it's about to serialize with a serializer while debugging the return trip from the JobController.

Finally, and I don't thinks this is an issue, I'm using some RestControllerAdvice extending the AbstractMappingJacksonResponseBodyAdvice which picks the jsonView I want to use based on the currentUser's role.

EDIT: I just commented out the controller advice, and it's still not calling my custom deserializer.

@RestControllerAdvice
@Slf4j
class SecurityJsonViewControllerAdvice extends AbstractMappingJacksonResponseBodyAdvice {


    @Override
    protected void beforeBodyWriteInternal(
            @NotNull MappingJacksonValue bodyContainer,
            @NotNull MediaType contentType,
            @NotNull MethodParameter returnType,
            @NotNull ServerHttpRequest request,
            @NotNull ServerHttpResponse response) {
        if (SecurityContextHolder.getContext().getAuthentication() != null && SecurityContextHolder.getContext().getAuthentication().getAuthorities() != null) {
            Collection<? extends GrantedAuthority> authorities = SecurityContextHolder.getContext().getAuthentication().getAuthorities();
            Class<?> viewClass = User.Role.jsonViewFrom(authorities);
            if (true || log.isDebugEnabled()) {
                log.debug("Deserializing using view {}", viewClass.getSimpleName());
            }
            bodyContainer.setSerializationView(viewClass);
        }
    }
}
Jazzepi :

I was able to fix my problem by doing two things.

@Component
public class PageJsonSerializer extends StdSerializer<Page> {

extending StdSerializer instead of JsonSerializer I actually don't know if this was part of the solution.

I think the real help came from registering the serializer by hand instead of relying on @JsonComponent.

So my ObjectMapperConfigurer looks like this now.

public class ObjectMapperConfigurer {
    public static ObjectMapper configureObjectMapper(ObjectMapper objectMapper) {
        return objectMapper.registerModules(
                // First three modules can be found here. https://github.com/FasterXML/jackson-modules-java8
                new Jdk8Module(), // support for other new Java 8 datatypes outside of date/time: most notably Optional, OptionalLong, OptionalDouble
                new JavaTimeModule(), // support for Java 8 date/time types (specified in JSR-310 specification)
                new ParameterNamesModule(), // Support for detecting constructor and factory method ("creator") parameters without having to use @JsonProperty annotation
                // Manually registering my serializer. 
                new SimpleModule().addSerializer(Page.class, pageJsonSerializer),
... all the same
}

I also removed the JsonComponentModule from my ObjectMapper since it seems to be broken.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=158454&siteId=1