Использование Wrapper Type для DTO весной + Джексон - PullRequest
1 голос
/ 03 февраля 2020

Я пытаюсь найти более или менее элегантный способ обработки http-операций PATCH в Spring MVC.

По сути, я хотел бы выполнить "двойную" десериализацию Джексона для JSON документ из тела запроса: один на карту, а другой на целевой POJO. В идеале я хотел бы выполнить это в одном PartialDto<T> экземпляре, где T - мой целевой тип DTO.

Лучше привести пример. Допустим, в настоящее время у меня есть это отображение PUT в контроллере REST:

    @PutMapping("/resource")
    public MyDto updateWhole(@RequestBody MyDto dto) {
        System.out.println("PUT: updating the whole object to " + dto);
        return dto;
    }

Моя идея состоит в том, чтобы создать тип PartialDto, который обеспечивал бы как представление POJO тела запроса, так и представление Map, как это:

    @PatchMapping("/resource")
    public MyDto updatePartial(@RequestBody PartialDto<MyDto> partial) {
        System.out.println("PATCH: partial update of the object to " + partial);

        final MyDto dto = partial.asDto();
        // Do stuff using the deserialized POJO

        final Map<String, Object> map = partial.asMap();
        // Do stuff as a deserialized map...

        return dto;
    }

Я надеюсь, что это позволит мне еще больше расширить реализацию PartialDto, чтобы я мог выполнять такие вещи:

        if (partial.hasAttribute("myAttribute")) {
            final String myAttribute = dto.getMyAttribute();
            // ...
        }

Или даже с помощью генератора метамоделей:

        if (partial.hasAttribute(MyDto_.myAttribute)) {
            final String myAttribute = dto.getMyAttribute();
            // ...
        }

Итак, вопрос 1020 * прост: Джексон может легко сопоставить документ JSON с POJO. Он также может легко сопоставить документ JSON с картой java. Как я могу сделать оба одновременно в объекте Wrapper, таком как мой PartialDto?

public class PartialDto<T> {
    private final Map<String, Object> map;
    private final T dto;

    PartialDto(Map<String, Object> map, T dto) {
        this.map = map;
        this.dto = dto;
    }

    public T asDto() {
        return this.dto;
    }

    public Map<String, Object> asMap() {
        return Collections.unmodifiableMap(this.map);
    }
}

Я пытался использовать GenericConverter как этот (который, конечно, я зарегистрировал в Spring MVC FormatterRegistry):

public class PartialDtoConverter implements GenericConverter {
    private final ObjectMapper objectMapper;

    public PartialDtoConverter(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    @Override
    public Set<ConvertiblePair> getConvertibleTypes() {
        return Collections.singleton(new ConvertiblePair(String.class, PartialDto.class));
    }

    @Override
    public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
        final Class<?> targetClazz = targetType.getResolvableType().getGeneric(0).getRawClass();
        final Map<String, Object> map;
        try {
            map = objectMapper.readValue((String) source, Map.class);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e); // FIXME
        }
        final Object dto = objectMapper.convertValue(map, targetClazz);
        return new PartialDto(map, dto) ;
    }
}

И этот преобразователь хорошо работает при тестировании непосредственно с использованием Spring ConversionService:

@SpringBootTest
class ConverterTest {

    @Autowired
    private ConversionService conversionService;

    @Test
    public void testPartialUpdate() throws Exception {
        final MyDto dto = new MyDto()
                .setIt("It");

        final PartialDto<MyDto> partialDto = (PartialDto<MyDto>) conversionService.convert(
                "{ \"it\": \"Plop\" }",
                new TypeDescriptor(ResolvableType.forClass(String.class), null, null),
                new TypeDescriptor(ResolvableType.forClassWithGenerics(PartialDto.class, MyDto.class), null, null)
        );

        Assertions.assertEquals("Plop", partialDto.asDto().getIt());
        Assertions.assertEquals("Plop", partialDto.asMap().get("it"));
    }
}

Однако он не работает в @RequestBody, как показано выше , Напоминание:

    @PatchMapping("/resource")
    public MyDto updatePartial(@RequestBody PartialDto<MyDto> partial) {
        // ...
    }

Любая идея приветствуется.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...