94621
94621

Reputation: 89

How to customize ObjectMapper based on attribute in request context?

I have a spring-boot app, that has one generic RestController for all api versions and objects, i.e:

@Controller
@Path("{version: v[1-9][0-9]*|latest}")
@RequiredArgsConstructor
public MyController {
    private final LoaderFactory loaderFactory;

    @PUT
    @Path("/{uri: .+}")
    public Response update(String requestBody,
                           @PathParam("version") String versionParam,
                           @PathParam("uri") String uri) {
        Loader loader = loaderFactory.getLoaderForVersion(versionParam);
        Introspector introspector = loader.unmarshal(requestBody, Introspector.class);
        return writeToDb(introspector);
    }

    private String writeToDb(Introspector introspector) {...}
}

Loader and Introspector are custom classes that use JAXB and Eclipse Persistence under the hood to parse the objects based on a xsd schema (one schema for each api version).

This design is working with defining both the request body and response body as String and doing the serialization and deserialization in the Controller. I'd like to change it thus, that it is handled in the background (by Jackson) and the method signature changes to (note the Introspector requestBody):

@PUT
@Path("/{uri: .+}")
public Response update(Introspector requestBody, ...) {...}

Since the deserialization of Introspector into an object depends on the api version, the out-of-the-box deserialization of Jackson doesn't work. It somehow has to be instructed to use the correct Loader instance based on the api version.

What I have tried

So far, I have implemented a custom IntrospectorDeserializer that implements ContextualDeserializer which works correctly in a test case:

  @Test
  public void deserialize() throws IOException, AAIUnmarshallingException {
    Loader loader = loaderFactory.getLoaderForVersion("v14");
    ObjectMapper mapper = new ObjectMapper();
    SimpleModule module = new SimpleModule();
    module.addDeserializer(Introspector.class, new IntrospectorDeserializer()); // IntrospectorDeserializer
                                                                                // is my custom class
    mapper.registerModule(module);

    mapper.setConfig(mapper.getDeserializationConfig().withAttribute("loader", loader));

    String customer = new String(Files.readAllBytes(Path.of("src/test/resources/customer.json")));
    Introspector introspector = mapper.readValue(customer, Introspector.class);
    String result = mapper.writeValueAsString(introspector);
    JSONAssert.assertEquals(customer, result, false);
  }

Here, the version-specific loader instance is passed in via mapper.setConfig(mapper.getDeserializationConfig().withAttribute("loader", loader)).

I don't know if I am heading in the right direction by implementing a custom deserializer. My question is now, how I would instruct Jackson to somehow make use of my loader class and use the correct loader based on the api version that is provided via the @PathParam?

Upvotes: 0

Views: 47

Answers (0)

Related Questions