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

Enhanced handling for @JsonView array annotation in the context of Rest controller method mapping #32084

Closed
faurad opened this issue Jan 22, 2024 · 2 comments
Assignees
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) status: invalid An issue that we don't feel is valid

Comments

@faurad
Copy link

faurad commented Jan 22, 2024

Affects: <all versions including latest>

This limitation should relate to the way @JsonView annotation was implemented in spring:
@#11815

@JsonView annotation allows to specify multiple views for a class or field using this syntax:

@JsonView({View1.class, View2.class, View3.class})
private java.util.List<Person> persons=null;

By design Spring does not interpret the syntax fully, as it is ignoring multiple view classes in the @JsonView array annotation {}

Code sample:


@JsonView({EventDate.EventPrivateViews.ClientEventServiceRequirement_PutView.class, ObjectPublicViews.Attributes_Roles.class})
@RequestMapping(value = "/eventDate/{eventDateId}", method = {RequestMethod.GET})
public ResponseEntity<EventDate> getEventDate(@PathVariable( name = "eventDateId") Integer eventDateId) {
	EventDate eventDate = ovEventDateCrudService.retrieveById(EventDate.class, eventDateId);
	ResponseEntity<EventDate> ret = new ObjectViewResponseEntity<>( eventDate, HttpStatus.OK );
	return ret;
}

ObjectPublicViews.Attributes_Roles.class (and any other view classes in the array) is ignored, just first one is taken into account
Code will behave like having just one view class:

@JsonView({EventDate.EventPrivateViews.ClientEventServiceRequirement_PutView.class})
@RequestMapping(value = "/eventDate/{eventDateId}", method = {RequestMethod.GET})
public ResponseEntity<EventDate> getEventDate(@PathVariable( name = "eventDateId") Integer eventDateId) {
	EventDate eventDate = ovEventDateCrudService.retrieveById(EventDate.class, eventDateId);
	ResponseEntity<EventDate> ret = new ObjectViewResponseEntity<>( eventDate, HttpStatus.OK );
	return ret;
        
}

This is how sample domain classes might look like:

public class EventDate extends ESDomainObject  
{ 

@JsonView(ObjectPublicViews.Attributes.class)
	private java.lang.Integer eventDateId=null;

@JsonView({ObjectPublicViews.Attributes_Roles.class, ObjectPrivateViews.Attributes_Roles_eventServiceCategories.class, EventPrivateViews.ClientEventServiceRequirement_PutView.class})
	private java.util.List<EventServiceCategory> eventServiceCategories=null;
}
EventDate inner class: 
public static final class EventPrivateViews {
	public static class ClientEventServiceRequirement_PutView extends ObjectPublicViews.Attributes {}
}

and down the road in :

public class EventServiceCategory extends ESDomainObject  { 

	@JsonView(ObjectPublicViews.Attributes.class)
	private java.lang.Integer serviceCategoryId=null;

@JsonView({ObjectPublicViews.Attributes_Roles.class, ObjectPrivateViews.Attributes_Roles_eventRequirements.class, EventPrivateViews.ClientEventServiceRequirement_PutView.class})
	private java.util.List<EventRequirement> eventRequirements=null;
} 

and so on to assemble an object graph (tree)

This limitation of Spring's handling of @JsonView in controller methods could not exactly be a defect but rather a design decision, where each endpoint corresponds to a specific view of the data. Maybe idea was to simplify the controller's logic by binding a single view to an endpoint.

However, since JSON does provide such annotation syntax, and because developers may have already defined all JSONViews for each particular association, it would be beneficial to construct a controller JSON view from existing defined views. Consider a scenario where you need to save an object graph to a database, and graph navigation is performed through reachability.

In such cases, a solution to this limitation in Spring is to statically create a new @JsonView (EventPrivateViews.ClientEventServiceRequirement_PutView.class in sample code) or add this new view class to an existing @JsonView annotation and repeat process for each class down the object graph tree using this new View class. This process must be repeated for each new graph view of the existing data (leading to domain recompilation and so on). Additionally, the number of combinations can grow to the point where they become unmanageable. This is the solution I adopted in the included sample classes above.
Instead, developers should be able to assemble a new object graph view from existing discrete association-defined views.

I understand that there might be other solutions to overcome this challenge, but it's worth noting that developers often use an injected ObjectMapper per controller instance and prefer not to handle that instance directly, let alone create new instances for each REST call (...out of question) to handle dynamic situations that cannot be managed by annotations.

So, it would be nice to be able to dynamically specify an array with classes for the JsonView annotation for the controller.

@JsonView({EventDate.EventPrivateViews.ClientEventServiceRequirement_PutView.class, ObjectPublicViews.Attributes_Roles.class})

In such case spring should analyze the view classes and spring ObjectMapper autowired instance should lazy initialize the object graph (tree) before serialization (as it does now), but this time taking into account all specified view classes. It should parse the annotations and create a union (set) for the final view with all related attributes. ObjectMapper should not retain any configuration state (like it correctly does now) for this dynamic view.

Some ideea:
ObjectWriter writer = objectMapper.writerWithView(spring_internally_assembled_view);

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Jan 22, 2024
@faurad faurad changed the title Incorrect handling for @JsonView array annotation in the context of Rest controller method maping (@RequestMapping) Enhaced handling for @JsonView array annotation in the context of Rest controller method maping (@RequestMapping) Jan 23, 2024
@sdeleuze sdeleuze self-assigned this Jan 23, 2024
@sdeleuze sdeleuze added the in: web Issues in web modules (web, webmvc, webflux, websocket) label Jan 23, 2024
@sdeleuze
Copy link
Contributor

sdeleuze commented Jan 23, 2024

As documented, the fact that Spring supports a single view per @JsonView is by design and there is no plan to change that, so I am likely going to decline your request.

@JsonView allows an array of view classes, but you can specify only one per controller method. If you need to activate multiple views, you can use a composite interface.

This is because Spring reuses Jackson @JsonView where this array was designed for the domain model use case, and you can see that ObjectMapper#writerWithView only takes a single class parameter.

ObjectPublicViews.Attributes_Roles.class (and any other view classes in the array) is ignored, just first one is taken into account

Spring should not silently ignore such @RequestMapping level @JsonView with an array, and should throw an error, like JsonViewRequestBodyAdvice or MappingJackson2MessageConverter do.

Could you please provide a reproducer that demonstrates that behavior as an attached project or a link to a repository?

@sdeleuze sdeleuze added the status: waiting-for-feedback We need additional information before we can continue label Jan 23, 2024
@sdeleuze sdeleuze changed the title Enhaced handling for @JsonView array annotation in the context of Rest controller method maping (@RequestMapping) Enhanced handling for @JsonView array annotation in the context of Rest controller method mapping Jan 23, 2024
@faurad
Copy link
Author

faurad commented Jan 24, 2024

You are right, I understand the design decision, I will close this and apologize for the time you spent.

Best,
Adrian

@faurad faurad closed this as completed Jan 24, 2024
@sbrannen sbrannen closed this as not planned Won't fix, can't repro, duplicate, stale Jan 24, 2024
@sbrannen sbrannen added status: invalid An issue that we don't feel is valid and removed status: waiting-for-feedback We need additional information before we can continue status: waiting-for-triage An issue we've not yet triaged or decided on labels Jan 24, 2024
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: invalid An issue that we don't feel is valid
Projects
None yet
Development

No branches or pull requests

4 participants