Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

InvalidDefinitionException with inheritance and Lists #267

Closed
vb-dci opened this issue Nov 5, 2019 · 7 comments
Closed

InvalidDefinitionException with inheritance and Lists #267

vb-dci opened this issue Nov 5, 2019 · 7 comments

Comments

@vb-dci
Copy link

vb-dci commented Nov 5, 2019

Hello,
I found a problem with deserialization on lists and inheritance. The problem occurs when you define an abstract method to deserialize objects.
Here's a small example, I hope it's ok for you that the example also uses spring:

abstract class BaseCrudController<Dto : BaseDto> {
    @PostMapping("/saveAll")
    @ResponseBody
    fun saveAsList(@RequestBody dtos: List<Dto>): List<Dto> = listOf()
}

@Controller
@RequestMapping("/api/v1/customer")
class CustomerController() : BaseCrudController<CustomerDto>()

Here's the class to deserialize:

class CustomerDto(var name: String?, var accountNumber: String?) : BaseDto()

abstract class BaseDto {
    var id: Long? = null
    var version: Int = 0
}

The error message is:
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.http.converter.HttpMessageConversionException: Type definition error: [simple type, class com.example.basecrudspringrestendpoint.presentation.dto.core.BaseDto]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinition Exception: Cannot construct instance of com.example.basecrudspringrestendpoint.presentation.dto.core.BaseDto (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information at [Source: (PushbackInputStream); line: 2, column: 3] (through reference chain: java.util.ArrayList[0])] with root cause

you can find the code for this also here https://github.com/vb-dci/jackson-spring-bug

@apatrida
Copy link
Member

apatrida commented Nov 5, 2019

@vb-dci I'm not sure this is a bug with the module nor Jackson. Spring is the one deciding the type of the class to deserialize into, and it is basing this off of the runtime view of

fun saveAsList(@RequestBody dtos: List<Dto>): List<Dto> = listOf() {...}`

At runtime the only thing that can be seen is that it is some Dto which is bound as inherits from BaseDTO and that is as much information as this method has that is reflectable at runtime. There is no way to know it is CustomerDto in this one special context, only at compile time is that known and only when referenced from CustomerDto.

@cowtowncoder ignoring the Kotlin piece. here is the main problem:

abstract class BaseCrudController<Dto : BaseDto> {

    @PostMapping("/saveAll")
    @ResponseBody
    fun saveAsList(@RequestBody dtos: List<Dto>): List<Dto> = listOf()
}

@Controller
@RequestMapping("/api/v1/customer")
class CustomerController() : BaseCrudController<CustomerDto>()

class CustomerDto(var name: String?, var accountNumber: String?) : BaseDto()

So the saveAsList is blowing up on the List<Dto> which is really List<out BaseDto> at runtime, and not List<CustomerDto> as expected by the author of this report. Not sure how this would work in any way without overriding the method to give it more type information at the CustomerController level

@cowtowncoder
Copy link
Member

If I understand this correctly, it is much due to frameworks like Spring not using something that can actually pass type resolution context (Jackson has JavaType which could be used, but JDK has nothing, and some other alternatives like Guava's do not allow programmatic construction).
Put another way: Jackson databind could resolve all types from controller class, but there is no mechanism for passing that in usable form. So Spring et al pass java.lang.reflect.Type which has type variable, but no real way to reliably resolve it.

If this is the case, this is not solvable.
There may be work-around of using explicit non-generic subtype...although not sure how it'd work here.

@vb-dci
Copy link
Author

vb-dci commented Nov 6, 2019

Thanks for helping. You are right, the same problem occurs with java.

@vb-dci vb-dci closed this as completed Nov 6, 2019
@vb-dci
Copy link
Author

vb-dci commented Nov 6, 2019

Actually, I need to reopen this bug. I have rewrote it with Java

public abstract class BaseController<Dto extends BaseDto> {
    @PostMapping("/saveAll")
    @ResponseBody
    public List<Dto> saveAll(@RequestBody List<Dto> dtos) { return dtos; }
}

@Controller
@RequestMapping("/api/customer")
public class CustomController extends BaseController<CustomerDto> {}

public class CustomerDto extends BaseDto {
    String name;
    public String getName() {return name; }
    public void setName(String name) {this.name = name; }
}

and it working fine. maybe someone will find a solution after all. here the full example https://github.com/vb-dci/jackson-spring-test

@vb-dci vb-dci reopened this Nov 6, 2019
@vitaB
Copy link

vitaB commented Nov 6, 2019

maybe loading the jackson-module-kotlin is messed up in the pom? The example works if you use java collections.

@PostMapping("/saveAll")
fun saveAll(@RequestBody dtos: java.util.List<Dto>): List<Dto> = listOf()

@cowtowncoder
Copy link
Member

Problem here is now that of framework: although problem can be shown in context of Spring, it can not be isolated to Jackson handling. So typically issue would be filed against Spring (since Spring controller implementation code uses Jackson in some way and defines exact calls made), and then if spring developers find a problem, they (or original reported) can file isolated example against Jackson.

@apatrida
Copy link
Member

apatrida commented Nov 7, 2019

@vitaB The issue would require debugging how Spring is using the module and would be best if you could reproduce using only Kotlin + this module without Spring. I don't want to learn the internal of Spring to check out this issue which doesn't, on first glance, appear to be related to the module because things like this can work fine when called in the right way.

If you can help eliminate the Spring-ness, or work through that team otherwise, it would help.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants