Java REST services – What are best practices regarding DTOs?

I’ve been working on a project that communicates with a service which sends very complicated REST responses. Current “best practice” on this team is that we use POJO DTOs to handle all data transferring. If we have a GET endpoint, it returns a POJO DTO, if it’s a PUT endpoint, we only accept data that fits a DTO etc. This works great for simple services, but has not worked great when we’re expecting incredibly complicated JSON objects being sent to us. The DTO package is currently 15 classes large and it’s not done yet; and this is only for one endpoint. One specific issue I’ve come across is that there are “flavors” of JSON objects that we have. Take for example:

{
  "Object" : {
    "ID" : int,
    "some other value" : int,
    "type" : "ObjectA",
    "largeDataArray" : (
      //other nested stuff
    )
  }
}

This skeleton object may have three or four “flavors” that are all the same except for the largeDataArray that is wildly different between cases. In order to deal with this we’re using jackson inheritance, but this is adding a lot of complexity to the project and doesn’t work for other instances where the type field doesn’t match up with the variety of largeDataArray we receive. Using jackson inheritance also has us having a @RequestBody annotation to points to the parent of a JsonType which is extended by the child POJO DTOs that we’re actually working with.

This has all left me wondering if there is a better way of handling these complicated JSON objects. There was a time where everyone just used JsonObject, but that lead to a lot of issues with readability and maintainability (people not commenting their code or poor documentation). The use of POJO DTOs has stuck because it is self-documenting. Is there a better way of handling this?

Edit: One proposal I have is to simply create one large proto-type DTO that allows us to fill in everything we want and leave everything we don’t want blank, but this has gotten pushback because our OpenAPI is not then able to show that only one type of largeDataArray object can be passed per call. This is certainly the simplest solution though.