Search code examples
javaspringrestspring-rest

Spring parsing JSON RequestBody: "Could not resolve type id" or "Root name does not match expected"


I'm building a REST API with Spring; well currently I'm failing to do so.

TL;DR

I get either this (error 1)

JSON parse error: Could not resolve type id 'test1' as a subtype of crm.zappes.core.template.domain.model.TemplateRequest: known type ids = [TemplateRequest]

or this (error 2)

JSON parse error: Root name ('test1') does not match expected ('TemplateRequest') for type crm.zappes.core.template.domain.model.TemplateRequest

Model

I used @JsonTypeInfo to wrap the class name around it; that leads to error 1.

{"TemplateRequest":{"test1":"Anakin","test2":"Skywalker"}}

If I use the default without this annotation the generated JSON doesn't have a wrapping root element which leads to error 2:

{"test1":"Anakin","test2":"Skywalker"}
@Data @Builder @NoArgsConstructor @AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
// With this I get error 1, without it error 2
@JsonTypeInfo(include = JsonTypeInfo.As.WRAPPER_OBJECT, use = JsonTypeInfo.Id.NAME)
public class TemplateRequest {
    private String test1;
    private String test2;
}

Controller

In this Controller Endpoint I want the JSON to be converted into a TemplateRequest Model Object.

@RestController
@RequestMapping("/zappes/")
public class TemplateController {
    @PostMapping(value = "/template/test", consumes = {MediaType.APPLICATION_JSON_VALUE})
    public ResponseEntity<String> testPost(@RequestBody TemplateRequest request) {
        return ResponseEntity.ok("Hello World");
    }
}

If I change it to @RequestBody String request it works fine and I see the 2 JSON variants (see above), so the endpoint mapping itself works. Spring just cannot parse the JSON into a model object. Which is kind of weird, because the JSON was also generated by the Spring REST framework. See next section.

Test

Here I'm sending the POST Call to the Controller.

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
class TemplateControllerIntegrationTests {
    @Test
    void testPost() {
        HttpHeaders headers = new HttpHeaders();
        headers.setBasicAuth("server_user", "server_password");

        var request = TemplateRequest.builder().test1("Anakin").test2("Skywalker").build();

        var requestEntity = new HttpEntity<>(request, headers);

        var restTemplate = new RestTemplate();
        var result = restTemplate.exchange("http://localhost:8083/zappes/template/test", HttpMethod.POST, requestEntity, String.class);

        Assertions.assertEquals("Hallo Welt", result.getBody());
    }
}

Solution

  • Apparently there is no default JSON Converter to I needed to create a little Configuration class:

    @Bean
    public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
        var jsonConverter = new MappingJackson2HttpMessageConverter();
        var objectMapper = new ObjectMapper();
        jsonConverter.setObjectMapper(objectMapper);
        return jsonConverter;
    }