I have a spring rest web app that contains a a generic rest controller like below. The GET methods are working fine having Jackson serializing the objects to JSON. However when I try to call the save method, the RequestBody parameter is being converted to LinkedHashMap instead of the type defined by the T generic type.
#RestController
public abstract class CrudAPI<T extends Object, ID extends Serializable>{
#Transactional
#RequestMapping(method = RequestMethod.POST, consumes = "application/json")
public ResponseEntity<Void> save(#RequestBody T entity){
service.save(entity);
return new ResponseEntity(HttpStatus.CREATED);
}
}
The JSON:
{
"id":null,
"version":null,
"name":"Copel",
"disabled":false,
"type":"P",
"labels":[
{
"id":null,
"version":null,
"name":"unidade consumidora"
}
]
}
I get the following error:
HTTP Status 500 - Request processing failed; nested exception is
org.springframework.beans.NotReadablePropertyException: Invalid
property 'version' of bean class [java.util.LinkedHashMap]: Could not
find field for property during fallback access!
The Spring configuration:
#Configuration
#Import(Application.class)
#EnableWebMvc
#ComponentScan(basePackages = {"br.com.doc2cloud"})
public class WebConfig extends WebMvcConfigurerAdapter implements WebApplicationInitializer {
#Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
private MappingJackson2HttpMessageConverter jacksonConverter() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new Hibernate5Module());
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
mapper.setDateFormat(new ISO8601DateFormat());
mapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE);
mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
MappingJackson2HttpMessageConverter jacksonConverter = new MappingJackson2HttpMessageConverter();
jacksonConverter.setObjectMapper(mapper);
return jacksonConverter;
}
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(jacksonConverter());
super.configureMessageConverters(converters);
}
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.setInitParameter("javax.servlet.jsp.jstl.fmt.localizationContext", "messages");
EnumSet<DispatcherType> dispatcherTypes = EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD);
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
characterEncodingFilter.setForceEncoding(true);
FilterRegistration.Dynamic characterEncoding = servletContext.addFilter("characterEncoding", characterEncodingFilter);
characterEncoding.addMappingForUrlPatterns(dispatcherTypes, true, "/*");
}
}
What is wrong with my code?
I don't think you can achieve what you want. Java generics mechanism works only in compile-time. After compilation generic types are erased and are replaced by actual types (Object in your case). There is no way your controller will understand to what type you try to parse JSON data.
The reason why you get LinkedHashMap is that Jackson uses it by default for "unknown" types. Typical JSON data is actually a key-value map and Jackson preserves attributes ordering - that's why linked hash map is used.
Which version of Jackson are you using? I upgraded to 2.7.3 and when using generics (I have a base controller with common logic for saving, listing, etc) I had the same issue. Rolling back to 2.6.5 allowed me to continue using my generic base class. I haven't yet researched the reason for the issue but rolling back fixed it for me.
Related
I have a Spring Boot REST controller with the method
#GetMapping(value = "/validate", produces = MimeTypeUtils.APPLICATION_JSON_VALUE)
#ResponseBody
public Result validate(
#ApiParam(value = "http://example.org/test", required = true) #RequestParam String iri,
#Context HttpServletRequest request
) {
return service.validate(iri);
}
...
And a custom object mapper configured as (Result class is part of a third-party library)
#Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, true);
SimpleModule module = new SimpleModule();
module.addSerializer(Result.class, new ResultSerializer());
mapper.registerModule(module);
return mapper;
}
Inside ResultSerializer (my custom implementation) I need to customize the serialization output based on the Accept-language header (e.g. from the bounding HttpServletRequest). I am currently only able to solve by not using Spring injection for the ObjectMapper, explicitly creating an ObjectMapper instance in my controller, executing it and returning the output. So:
Can an Accept-language header be obtained in the ObjectMapper instance injected as a Spring bean?
A possibility (not knowing exactly what you are trying to customize): You could try making your ResultSerializer request-scoped.
#Bean
#RequestScope
public ResultSerializer resultSerializer() {
return new ResultSerializer();
}
Then you can simply inject your HttpServletRequest into the ResultSerializer.
public class ResultSerializer {
#Autowired
private HttpServletRequest request;
}
Thanks to Marco, I managed to adjust slightly his solution to get it working using:
#Bean
#RequestScope
public ObjectMapper objectMapper(#Autowired HttpServletRequest request) {
...
module.addSerializer(Result.class, new ResultSerializer(request.getLocale().toLanguageTag()));
...
}
There is a class as follow:
class A {
#JsonProperty("first_name")
String firstName;
}
And a MVC controller:
public A createNewA(A a){
...
}
I expect the following REST create an instance of A with first name:
POST: /path/to/resouce
Form data
first_name: Ali
But the first name is null. By the way, the following request works fine:
POST: /path/to/resouce
Form data
firstName: Ali
As I debug, ServletModelAttributeMethodProcessor is used to resolve parameter and where objects are Considered as bean.
Is there any parameter resolver to check jackson annotation?
Jackson is used to parse and serialize JSON. You're sending x-www-form-urlencoded data. So Jackson is irrelevant.
If you send a JSON request body, and thus annotate the a parameter with #RequestBody, then Jackson will be used, and will honor the annotation.
Default resolver of spring-mvc to convert method arguments to bean objects uses exactly name of fields in the bean to search in key-values sent in the request. If you want another behavior you should implement yours resolver. To do that you should implement HandlerMethodArgumentResolver which has two main method.
In your case which you want resolver behaves similar to jackson deserializer you could implement such resolver as follow. This resolver create an map of key-values from parameters of sent request then uses jackson mapper to fill a bean object by given key-values:
public class BeanObjectParameterResolver implements HandlerMethodArgumentResolver {
#Inject
ObjectMapper mapper;
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
Map<String, Object> params = new HashMap<>();
Iterator<String> enumName = webRequest.getParameterNames();
while(enumName.hasNext()){
String name = enumName.next();
params.put(name, webRequest.getParameter(name));
}
// jackson 1.9 and before
// mapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// or jackson 2.0
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Object obj = mapper.convertValue(params, parameter.getParameterType());
return obj;
}
#Override
public boolean supportsParameter(MethodParameter parameter) {
return !BeanUtils.isSimpleProperty(parameter.getParameterType());
}
}
I want to support both snake_case and camelCase in the json interface of my rest API.
Jackson uses camelCase by default. I know there are many ways of making Jackson to use snake_case:
Annotating properties individually: #JsonProperty("first_name")
Annotating classes with: #JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
Applying it to the whole application inside application.properties:
spring.jackson.property-naming-strategy=SNAKE_CASE
But I haven't found information about how to allow the consumer to request the desired case. For example by using content negotiation (Accepts, Content-Type headers)
Lets suppose we have the following example:
#RequestMapping("/my-resource")
public interface ApiController {
#RequestMapping(method = RequestMethod.POST, produces = {"application/json"})
MyResource createResource(#RequestBody MyResource myResource);
#GetMapping(value="/{resourceId}", produces = {"application/json"})
MyResource readResource(#PathVariable String resourceId);
}
So one use case could be to use custom json media-type and provide content negotiation headers like:
Accepts: application/vnd.snake+json
and let the default application/json to use camelCase.
But I cannot find the way of making jackson to use two different Mapper configurations based on the content negotiation.
Good question, I would suggest to take a look into content negotiation and http message converter topics.
It seems you can configure two instances of MappingJackson2HttpMessageConverter
to produce application/vnd.snake+json or application/json.
Set it with specific media type and differently configured ObjectMapper
Code modification can looks like:
MyMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter
{
public MyMappingJackson2HttpMessageConverter(ObjectMapper objectMapper, MediaType mediaType)
{
super(objectMapper, mediaType);
}
}
Configuration can looks like:
#EnableWebMvc
#Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void configureMessageConverters(
List<HttpMessageConverter<?>> converters) {
converters.add(new MyMappingJackson2HttpMessageConverter(snake, new MediaType("application", "vnd.snake+json")));
converters.add(new MappingJackson2HttpMessageConverter(camel, MediaType.APPLICATION_JSON));
super.configureMessageConverters(converters);
}
...
As per new version of spring boot, I end up doing this way -
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverterSnakeCase = new MappingJackson2HttpMessageConverter();
mappingJackson2HttpMessageConverterSnakeCase.setSupportedMediaTypes(Arrays.asList(new MediaType("application", "vnd.snake+json")));
mappingJackson2HttpMessageConverterSnakeCase.setObjectMapper(objectMapperSnakeCase());
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverterCamelCase = new MappingJackson2HttpMessageConverter();
mappingJackson2HttpMessageConverterCamelCase.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON));
mappingJackson2HttpMessageConverterCamelCase.setObjectMapper(objectMapperSnakeCase());
converters.add(mappingJackson2HttpMessageConverterCamelCase);
converters.add(mappingJackson2HttpMessageConverterSnakeCase);
WebMvcConfigurer.super.configureMessageConverters(converters);
}
I need to add a custom Jackson deserializer for java.lang.String to my Spring 4.1.x MVC application. However all answers (such as this) refer to configuring the ObjectMapper for the complete web application and the changes will apply to all Strings across all #RequestBody in all controllers.
I only want to apply the custom deserialization to #RequestBody arguments used within particular controllers. Note that I don't have the option of using #JsonDeserialize annotations for the specific String fields.
Can you configure custom deserialization for specific controllers only?
To have different deserialization configurations you must have different ObjectMapper instances but out of the box Spring uses MappingJackson2HttpMessageConverter which is designed to use only one instance.
I see at least two options here:
Move away from MessageConverter to an ArgumentResolver
Create a #CustomRequestBody annotation, and an argument resolver:
public class CustomRequestBodyArgumentResolver implements HandlerMethodArgumentResolver {
private final ObjectMapperResolver objectMapperResolver;
public CustomRequestBodyArgumentResolver(ObjectMapperResolver objectMapperResolver) {
this.objectMapperResolver = objectMapperResolver;
}
#Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.getParameterAnnotation(CustomRequestBody.class) != null;
}
#Override
public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
if (this.supportsParameter(methodParameter)) {
ObjectMapper objectMapper = objectMapperResolver.getObjectMapper();
HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
return objectMapper.readValue(request.getInputStream(), methodParameter.getParameterType());
} else {
return WebArgumentResolver.UNRESOLVED;
}
}
}
#CustomRequestBody annotation:
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface CustomRequestBody {
boolean required() default true;
}
ObjectMapperResolver is an interface we will be using to resolve actual ObjectMapper instance to use, I will discuss it below. Of course if you have only one use case where you need custom mapping you can simply initialize your mapper here.
You can add custom argument resolver with this configuration:
#Configuration
public class WebConfiguration extends WebMvcConfigurerAdapter {
#Bean
public CustomRequestBodyArgumentResolver customBodyArgumentResolver(ObjectMapperResolver objectMapperResolver) {
return new CustomRequestBodyArgumentResolver(objectMapperResolver)
}
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(customBodyArgumentResolver(objectMapperResolver()));
}
}
Note: Do not combine #CustomRequestBody with #RequestBody, it will be ignored.
Wrap ObjectMapper in a proxy that hides multiple instances
MappingJackson2HttpMessageConverter is designed to work with only one instance of ObjectMapper. We can make that instance a proxy delegate. This will make working with multiple mappers transparent.
First of all we need an interceptor that will translate all method invocations to an underlying object.
public abstract class ObjectMapperInterceptor implements MethodInterceptor {
#Override
public Object invoke(MethodInvocation invocation) throws Throwable {
return ReflectionUtils.invokeMethod(invocation.getMethod(), getObject(), invocation.getArguments());
}
protected abstract ObjectMapper getObject();
}
Now our ObjectMapper proxy bean will look like this:
#Bean
public ObjectMapper objectMapper(ObjectMapperResolver objectMapperResolver) {
ProxyFactory factory = new ProxyFactory();
factory.setTargetClass(ObjectMapper.class);
factory.addAdvice(new ObjectMapperInterceptor() {
#Override
protected ObjectMapper getObject() {
return objectMapperResolver.getObjectMapper();
}
});
return (ObjectMapper) factory.getProxy();
}
Note: I had class loading issues with this proxy on Wildfly, due to its modular class loading, so I had to extend ObjectMapper (without changing anything) just so I can use class from my module.
It all tied up together using this configuration:
#Configuration
public class WebConfiguration extends WebMvcConfigurerAdapter {
#Bean
public MappingJackson2HttpMessageConverter jackson2HttpMessageConverter() {
return new MappingJackson2HttpMessageConverter(objectMapper(objectMapperResolver()));
}
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(jackson2HttpMessageConverter());
}
}
ObjectMapperResolver implementations
Final piece is the logic that determines which mapper should be used, it will be contained in ObjectMapperResolver interface. It contains only one look up method:
public interface ObjectMapperResolver {
ObjectMapper getObjectMapper();
}
If you do not have a lot of use cases with custom mappers you can simply make a map of preconfigured instances with ReqeustMatchers as keys. Something like this:
public class RequestMatcherObjectMapperResolver implements ObjectMapperResolver {
private final ObjectMapper defaultMapper;
private final Map<RequestMatcher, ObjectMapper> mapping = new HashMap<>();
public RequestMatcherObjectMapperResolver(ObjectMapper defaultMapper, Map<RequestMatcher, ObjectMapper> mapping) {
this.defaultMapper = defaultMapper;
this.mapping.putAll(mapping);
}
public RequestMatcherObjectMapperResolver(ObjectMapper defaultMapper) {
this.defaultMapper = defaultMapper;
}
#Override
public ObjectMapper getObjectMapper() {
ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = sra.getRequest();
for (Map.Entry<RequestMatcher, ObjectMapper> entry : mapping.entrySet()) {
if (entry.getKey().matches(request)) {
return entry.getValue();
}
}
return defaultMapper;
}
}
You can also use a request scoped ObjectMapper and then configure it on a per-request basis. Use this configuration:
#Bean
public ObjectMapperResolver objectMapperResolver() {
return new ObjectMapperResolver() {
#Override
public ObjectMapper getObjectMapper() {
return requestScopedObjectMapper();
}
};
}
#Bean
#Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public ObjectMapper requestScopedObjectMapper() {
return new ObjectMapper();
}
This is best suited for custom response serialization, since you can configure it right in the controller method. For custom deserialization you must also use Filter/HandlerInterceptor/ControllerAdvice to configure active mapper for current request before the controller method is triggered.
You can create interface, similar to ObjectMapperResolver:
public interface ObjectMapperConfigurer {
void configureObjectMapper(ObjectMapper objectMapper);
}
Then make a map of this instances with RequstMatchers as keys and put it in a Filter/HandlerInterceptor/ControllerAdvice similar to RequestMatcherObjectMapperResolver.
P.S. If you want to explore dynamic ObjectMapper configuration a bit further I can suggest my old answer here. It describes how you can make dynamic #JsonFilters at run time. It also contains my older approach with extended MappingJackson2HttpMessageConverter that I suggested in comments.
Probably this would help, but it ain't pretty. It would require AOP. Also I did not validate it.
Create a #CustomAnnotation.
Update your controller:
void someEndpoint(#RequestBody #CustomAnnotation SomeEntity someEntity);
Then implemment the AOP part:
#Around("execution(* *(#CustomAnnotation (*)))")
public void advice(ProceedingJoinPoint proceedingJoinPoint) {
// Here you would add custom ObjectMapper, I don't know another way around it
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
String body = request .getReader().lines().collect(Collectors.joining(System.lineSeparator()));
SomeEntity someEntity = /* deserialize */;
// This could be cleaner, cause the method can accept multiple parameters
proceedingJoinPoint.proceed(new Object[] {someEntity});
}
You can create custom deserializer for your String data.
Custom Deserializer
public class CustomStringDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String str = p.getText();
//return processed String
}
}
Now suppose the String is present inside a POJO use #JsonDeserialize annotation above the variable:
public class SamplePOJO{
#JsonDeserialize(using=CustomStringDeserializer.class)
private String str;
//getter and setter
}
Now when you return it as a response it will be Deserialized in the way you have done it in CustomDeserializer.
Hope it helps.
You could try Message Converters.
They have a context about http input request (for example, docs see here, JSON). How to customize you could see here.
Idea that you could check HttpInputMessage with special URIs, which used in your controllers and convert string as you want.
You could create special annotation for this, scan packages and do it automatically.
Note
Likely, you don't need implementation of ObjectMappers. You can use simple default ObjectMapper to parse String and then convert string as you wish.
In that case you would create RequestBody once.
You can define a POJO for each different type of request parameter that you would like to deserialize. Then, the following code will pull in the values from the JSON into the object that you define, assuming that the names of the fields in your POJO match with the names of the field in the JSON request.
ObjectMapper mapper = new ObjectMapper();
YourPojo requestParams = null;
try {
requestParams = mapper.readValue(JsonBody, YourPOJO.class);
} catch (IOException e) {
throw new IOException(e);
}
I have the following object model in my Spring MVC (v3.2.0.RELEASE) web application:
public class Order {
private Payment payment;
}
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = As.WRAPPER_OBJECT)
#JsonSubTypes.Type(name = "creditCardPayment", value = CreditCardPayment.class)
public interface Payment {}
#JsonTypeName("creditCardPayment")
public class CreditCardPayment implements Payment {}
When I serialise the Order class to JSON, I get the following result (which is exactly what I want):
{
"payment" : {
"creditCardPayment": {
...
}
}
Unfortunately, if I take the above JSON and try to de-serialise it back into my object model, I get the following exception:
Could not read JSON: Could not resolve type id 'creditCardPayment'
into a subtype of [simple type, class Payment] at [Source:
org.apache.catalina.connector.CoyoteInputStream#19629355; line: 1,
column: 58] (through reference chain: Order["payment"]); nested
exception is com.fasterxml.jackson.databind.JsonMappingException:
Could not resolve type id 'creditCardPayment' into a subtype of
[simple type, class Payment] at [Source:
org.apache.catalina.connector.CoyoteInputStream#19629355; line: 1,
column: 58] (through reference chain: Order["payment"])
My application is configured via Spring JavaConf, as follows:
#Configuration
#EnableWebMvc
public class AppWebConf extends WebMvcConfigurerAdapter {
#Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(Include.NON_NULL);
objectMapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false);
return objectMapper;
}
#Bean
public MappingJackson2HttpMessageConverter mappingJacksonMessageConverter() {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(objectMapper());
return converter;
}
#Bean
public Jaxb2RootElementHttpMessageConverter jaxbMessageConverter() {
return new Jaxb2RootElementHttpMessageConverter();
}
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(jaxbMessageConverter());
converters.add(mappingJacksonMessageConverter());
}
}
For testing, I have a controller with 2 methods, one returns an Order for HTTP GET request (this one works) and one that accepts an Order via a HTTP POST (this one fails), e.g.
#Controller
public class TestController {
#ResponseBody
#RequestMapping(value = "/test", method = RequestMethod.GET)
public Order getTest() {}
#RequestMapping(value = "/test", method = RequestMethod.POST)
public void postTest(#RequestBody order) {}
}
I have tried all suggestions from the various discussions on SO but so far had no luck. Can anyone spot what I'm doing wrong?
Try to register subtype using ObjectMapper.registerSubtypes instead of using annotations
The method registerSubtypes() works!
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="type")
public interface Geometry {
//...
}
public class Point implements Geometry{
//...
}
public class Polygon implements Geometry{
//...
}
public class LineString implements Geometry{
//...
}
GeoJson geojson= null;
ObjectMapper mapper = new ObjectMapper();
mapper.disable(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper.registerSubtypes(Polygon.class,LineString.class,Point.class);
try {
geojson=mapper.readValue(source, GeoJson.class);
} catch (IOException e) {
e.printStackTrace();
}
Note1: We use the Interface and the implementing classes. I fyou want jackson to de-serialize the classes as per their implementing classes, you have to register all of them using ObjectMapper's "registerSubtypes" method.
Note2: In addition you use, " #JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="type")" as annotation with your Interface.
You can also define the order of properties when mapper writes a value of your POJO as a json.
This you can do using below annotation.
#JsonPropertyOrder({"type","crs","version","features"})
public class GeoJson {
private String type="FeatureCollection";
private List<Feature> features;
private String version="1.0.0";
private CRS crs = new CRS();
........
}
Hope this helps!
I had a similar issue while working on a dropwizard based service. I don't fully understand why things didn't work for me in the same way that the dropwizard code works, but I know why the code in the original post doesn't work. #JsonSubTypes wants an array of sub types, not a single value. So if you replace the line...
#JsonSubTypes.Type(name = "creditCardPayment", value = CreditCardPayment.class)
with...
#JsonSubTypes({ #JsonSubTypes.Type(name = "creditCardPayment", value = CreditCardPayment.class) })
I believe your code will work.
For those that are having this same error message pop up, you may be having an issue with the subtypes being discovered. Try adding a line like the one above or looking for issue with the discovery of the classes that have the #JsonTypeName tag in them.
Rashmin's answer worked, and I found an alternative way to avoid the com.fasterxml.jackson.databind.JsonMappingException: Could not resolve type id into a subtype of Blah issue without needing to use registerSubtypes. What you can do is add the following annotation to the parent class:
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "type")
Note that the difference is JsonTypeInfo.Id.CLASS instead of JsonTypeInfo.Id.NAME. The downside is that the created JSON will contain the entire class name including the full namespace. The upside is that you don't have to worry about registering subtypes.
In my case I had added defaultImpl = SomeClass.class to #JsonTypeInfo and was trying to convert it SomeClass2.class
Encountered the same error and used the equivalent of the below JSON (instead of CreditCardPayment used my class name) as the input for deserializer and it worked:
{
"type": "CreditCardPayment",
...
}
In my case it was an old version of jackson-databind. I solved with version 2.11.0