Home > Mobile >  Spring - How to pass different types at once in JSON using PATCH
Spring - How to pass different types at once in JSON using PATCH

Time:12-16

i have a PATCH mapping method that currently accepts a Map<String, Object> as @ResponseBody, because i've seen ReflectionUtils as one of ways to implement the PatchMapping, but when I want to pass for example String, Integer and Long, then JSON doesn't distinguish which one are which one. I would like to be able to pass EntityID which is Long, some Value which is Integer and some Strings at once.

This is my current Controller method

@PatchMapping("/grades/{id}")
public GradeEto partialUpdate(@PathVariable("id") final Long id, @RequestBody Map<String, Object> updateInfo) {
    return gradeService.partialUpdate(id, updateInfo);
}

This is main part of the ReflectionUtils update

@Override
public GradeEto partialUpdate(Long id, Map<String, Object> updateInfo) {

    Grade grade = gradeRepository.findById(id).get();
    GradeEto gradeeto = GradeMapper.mapToETO(grade);
    updateInfo.forEach((key, value) -> {
        Field field = ReflectionUtils.findField(GradeEto.class, key);
        field.setAccessible(true);
        ReflectionUtils.setField(field, gradeeto, value);
    });

And i want to be able to pass for example

{
    "value": 2,   <-- Integer
    "comment": "Test Idea",   <-- String
    "subjectEntityId": 2  <-- Long
}

But it gives me IllegalArgumentException currently

How should i do it? And if im doing it the wrong way then how should I do it?

CodePudding user response:

You can make use of the functionality offered by Jackson to perform the partial update.

For that, instead of reading the data to patch as a Map you specify in your Controller the type of the request body as ObjectNode:

@PatchMapping("/grades/{id}")
public GradeEto partialUpdate(@PathVariable("id") final Long id,
                              @RequestBody ObjectNode updateInfo) {
    
    return gradeService.partialUpdate(id, updateInfo);
}

Then inside the Service to operate with ObjectNodes you need to turn GradeEto instance into a node tree using ObjectMapper.valueToTree() (assuming that Service class is a managed Bean you can inject ObjectMapper into it instead of instantiating it locally).

Method ObjectNode.fields() can be used to iterate over the fields and corresponding values of updateInfo. Methods isNull() and isEmpty(), which ObjectNode derives from its super types, would be handy to check if a particular property needs to be updated.

Then, finally, you would need to parse the ObjectNode back into GradeEto type.

So the code in the Service might look like this:

@Service
public class GradeService {

    private ObjectMapper mapper;
    
    public GradeEto partialUpdate(Long id, ObjectNode updateInfo) throws IOException {

        Grade grade = gradeRepository.findById(id).get();
        GradeEto gradeeto = GradeMapper.mapToETO(grade);
        
        ObjectNode gradeEtoTree = mapper.valueToTree(gradeeto);
        
        updateInfo.fields().forEachRemaining(e -> {
            if (!e.getValue().isNull() && !e.getValue().isEmpty())
                gradeEtoTree.replace(e.getKey(), e.getValue());
        });
        
        return mapper
            .readerFor(GradeEto.class) // creates an ObjectReader
            .readValue(gradeEtoTree);
    }
}

Note: it's worth to draw the reader's attention to the fact we also need to update the information in the Database. This logic is missing in both the Question and this Answer, since it's not relevant to the narrow technical problem of extracting the data that should be patched from JSON, but we definitely need to save the patched object (for instance a method that produces patched object can be wrapped with another method responsible for retrieving/updating the data in the database).

CodePudding user response:

I do believe org.springframework.cglib.beans.BeanMap can solve your problem in elegant way:

public static void main(String[] args) throws Exception {
    String json = "{\n"  
            "    \"value\": 2,\n"  
            "    \"comment\": \"Test Idea\",\n"  
            "    \"subjectEntityId\": 20\n"  
            "}";


    Grade grade = new Grade();
    BeanMap map = BeanMap.create(grade);

    map.putAll(new ObjectMapper().readValue(json, Map.class));


    assertThat(grade.value)
            .isEqualTo(2);
    assertThat(grade.subjectEntityId)
            .isEqualTo(20);
    assertThat(grade.comment)
            .isEqualTo("Test Idea");


}


@Data
static class Grade {

    int value;
    String comment;
    long subjectEntityId;
}

  • Related