Я пытаюсь найти более или менее элегантный способ обработки 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) {
// ...
}
Любая идея приветствуется.