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

Generic class and @JsonUnwrapped causes failure to deserialize JSON in some cases #22857

Closed
yonigibbs opened this issue Apr 30, 2019 · 2 comments
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) status: duplicate A duplicate of another issue

Comments

@yonigibbs
Copy link

I have a generic class which wraps another generic class which itself wraps a couple of other classes. I want the JSON from all these to be "flattened", so I use the @JsonUnwrapped annotation. I can then GET and POST the flattened JSON and it all works fine. But if I then introduce some abstract (generic) REST controller, so that the method which receives the POSTed data is in a generic class, Spring fails to deserialize the JSON correctly.

For clarity, here's some code that explains the situation...

First, I have some DTOs representing information about a user:

public class UserBasicInfo {
    private String name;

    // ... getters and setters
}

public class UserExtendedInfo {
    private String roleName;

    // ... getters and setters
}

I then have a generic class that I use to combine this "basic" and "extended" information:

public class ItemInfo<Basic, Extended> {
    @JsonUnwrapped
    private Basic basicInfo;

    @JsonUnwrapped
    private Extended extendedInfo;

    // ... getters and setters
}

I then have a generic class which wraps the generic class above and adds more information to it (e.g an ID):

public class SavedItem<Id, Info> {
    private Id id;

    @JsonUnwrapped
    private Info info;

    // ... getters and setters
}

In the end, what I work with is an object of this type: SavedItem<Integer, ItemInfo<UserBasicInfo, UserExtendedInfo>>

If I then have a controller which receives an item of that type, as shown below, it all works OK:

@RestController
public class UserRestController {
    @PostMapping
    public void handle(@RequestBody SavedItem<Integer, ItemInfo<UserBasicInfo, UserExtendedInfo>> item) {
    }
}

However, if I declare that method instead in an abstract generic base class, as shown below, it doesn't quite work:

public abstract class AbstractRestController<Id, Basic, Extended> {
    public void handle(SavedItem<Id, ItemInfo<Basic, Extended>> item) {
        // Here, item.getInfo().getBasicInfo() returns null, as does getExtendedInfo()
    }
}

@RestController
public class UserRestController extends AbstractRestController<Integer, UserBasicInfo, UserExtendedInfo> {
}

What happens is that the basicInfo and extendedInfo items are left null as the JSON fails to be deserialized correctly.

The issue does not occur if:

  • The concrete class, UserRestController, explicitly implements the method(even if it just overrides it and then calls the super class's implementation),

Or

  • There is only one generic class rather than two, e.g. if the method receives ItemInfo<Basic, Extended> instead of SavedItem<Id, ItemInfo<Basic, Extended>>

Or

  • I don't use @JsonUnwrapped (meaning the JSON has a couple of extra levels of nesting within it).

The reason I think this might be an issue with Spring rather than Jackson is because if I put a breakpoint in AbstractJackson2HttpMessageConverter.readJavaType() (line 239) I can see that when the Jackson object mapper is called, the javaType argument which is passed to it doesn't have any of the generic information in the ItemInfo class. Whereas when that same line of code is hit when the REST controller method is implemented in the concrete class, then the full generic type information is correctly passed into Jackson. However that's just a guess, so if you think this is actually a Jackson issue let me know and I'll log an issue with them.

A git repo where the issue can be replicated is available here. Details of how to replicate it are provided in the readme in that repo.

The version information is:

  • Spring Boot 2.1.4.RELEASE
  • Spring Framework 5.1.6.RELEASE
  • Jackson 2.9.8
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Apr 30, 2019
@rstoyanchev rstoyanchev added the in: web Issues in web modules (web, webmvc, webflux, websocket) label Nov 12, 2021
@poutsma
Copy link
Contributor

poutsma commented Sep 29, 2023

After some digging, the core of the issue seems to be that GenericTypeResolver.resolveType(Type, Class) is not able to resolve parameterization of a base class methods that has more than one level of generics, so that A<B<C>> will be resolved to A<B<?>>.

The main reason seems to be here:

Class<?>[] generics = new Class<?>[parameterizedType.getActualTypeArguments().length];

To support nested generics, the generics variable on this line cannot be a Class array, as classes do not contain parameterization info. However, changing that array from Class to ResolvableType, and using the other static ResolvableType.forClassWithGenerics method results in a whole bunch of unrelated test failures, so that does not seem to be the way forward either.

When used on the method of a class itself (i.e. not a base class method), the method does return the correct result, because that code is using a different code path, because hasUnresolvableGenerics returns false. See

.

@snicoll
Copy link
Member

snicoll commented Sep 29, 2023

@yonigibbs thanks for the sample. Thanks to the help of @poutsma, we've been able to confirm that this is a duplicate of #30079 in the sense that merging this also fixes your test. Please watch that issue for further updates.

@snicoll snicoll closed this as not planned Won't fix, can't repro, duplicate, stale Sep 29, 2023
@snicoll snicoll added status: duplicate A duplicate of another issue and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Sep 29, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) status: duplicate A duplicate of another issue
Projects
None yet
Development

No branches or pull requests

5 participants