I have an existing REST API in a spring boot project that looks somewhat like this:
#GetMapping(value = "/api/projects")
public List<Project> getProjects() {
List<Project> projects = projectsRepository.findAll();
List<Project> processed = processProjects(projects);
return processed;
}
When this method is called the returned JSON response looks something like this:
{
"JSON":"[
{
"id":"aaa",
"simple":"SYMBOLIC_VALUE_BBB",
"nested1":{
"field1":"SYMBOLIC_VALUE_C1",
"field2":"nonSymbolicValueC2",
"field3":"SYMBOLIC_VALUE_C3"
},
"nested2":{
"fieldA":"SYMBOLIC_VALUE_DDD"
}
},
...
]",
"mode":"application/json"
}
The symbolic values are being translated into a human readable form in the frontend. Everything works fine. But now I also need a second version of this method that does the translation on the backend side. Something like this:
#GetMapping(value = "/api/v2/projects")
public String getProjects() {
List<Project> projects = projectsRepository.findAll();
String projectsAsJson = ???
String processedJson = processProjectsJson(projectsAsJson);
return processedJson;
}
What would I put where the three Question Marks (???) are? I want to use the same json serialization that is used automagically by the Spring Framework. It should be robust against any configuration changes that may happen in the future.
Thank you very much.
Add a attribute ObjectMapper in your Controller, use dependency injection to get it, and then use : mapper.writeValueAsString(myObject);
Something like that:
#Autowired
private ObjectMapper mapper;
#GetMapping(value = "/api/v2/projects")
public String getProjects() {
List<Project> projects = projectsRepository.findAll();
String projectsAsJson = mapper.writeValueAsString(projects);
String processedJson = processProjectsJson(projectsAsJson);
return processedJson;
}
Let me know if it is not working.
Related
I am developing a Java backend application using Spring Boot. My controller needs to receive this type of object as input with an HTTP POST call:
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class Input {
private String name;
private String surname;
private String fiscalCode;
}
obviously these data arrive thanks to a JSON made like this:
{
"name":"John",
"surname":"Smith",
"fiscalCode":"XXXXXXXXX"
}
This is an example of a controller handling POST containing this body:
#PostMapping(produces = { "application/json" })
public ResponseEntity<Object> myController(#RequestBody Input myInput) {
// code....
}
The question is: how can I elegantly (without using a switch case or a series of if-else) detect all fields that are "" or null in my myInput object, and return a string that warns for example that "name, surname fields missing"?
What is the best way to do this? I've heard it's okay to use #ControllerAdvice, but I don't know how to do it.
Check out the #Validate annotation.
e.g. https://www.baeldung.com/spring-boot-bean-validation
what I am trying to do is,
If I take one pojo class like
#Entity
#Table(name = "property_table")
public class Property {
#Id
#Column(name = "property_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int propertyId;
#Column(name = "property_name")
private String propertyName;
#Column(name = "property_type")
private String propertyType;
}
In RestController I wrote Two Methods like
#GetMapping(value = "/getProperties", produces = { "application/json",
"application/xml" }, consumes = { "application/xml", "application/json" })
#ResponseBody
public List<Property> getProperties() {
//some code
}
#GetMapping(value = "/getPropertyById", produces = { "application/json",
"application/xml" }, consumes = { "application/xml", "application/json" })
#ResponseBody
public Property getPropertyById() {
//some code
}
So, hear what I am trying to do is
for first api method I want return json like some parameters from Property pojo class i.e., like
for getProperties api method
{
"property":[
{
"propertyId":001,
"propertyName":"PROPERTY 1"
},
{
"propertyId":002,
"propertyName":"PROPERTY 2"
}
],
In the Above json I want to return only two parameters i.e propertyId,propertyName and remaining parameter i.e propertyType I dont want to retun in json.
How to return like that?
and for the second api method I want to return all three parameters. i.e., like below
for getPropertyById api method
{
"propertyId":001,
"propertyName":"PROPERTY 1",
"propertyType:"PROPERTY_TYPE 1"
},
how to maintain different json response using same pojo class with different parameters for different api methods.
please help me to solve this isuue.
Thanks.
REST API under/over-fetching is a well-known problem. There's only two (classical ways) to handle that.
The first one is to build one model per each attribute visibility state. So, in your case, you'll need to create two different models (this kind of models are called DTO - Data Transfert Object). One model will have a propertyType attribute, the other will not. The model Property you've shared shows that you use the same class as entity and as transfert object. This solution will add some complexity to your app because you will have to implement some mappers to convert your entity to a corresponding DTO.
The second one is to accept that you send an attribute that will not be useful (be aware of the over-fetching). This solution is often the most adopted one. The cons of this solution is when you don't want to send something to your client (imagine a User model, you want to get the password from your client but you don't want to sent it back to it). Another obvious negative point is that the transactions will be larger but it is negligible in most cases
I would strongly advice you to keep your #Entity isolated in the 'db' layer. So that changes on the database side don't affect your API and vice versa. Also, you will have much better control over what data is exposed in your API. For your needs you can create 2 true DTOs, like PropertyDto and PropertyDetailsDto (or using private fields and getters/setters).
public class PropertyDto {
public String propertyId;
public String propertyName;
}
public class PropertyDetailsDto extends PropertyDto {
public String propertyType;
}
Map your #Entity to a specific dto corresponding to your needs.
EDIT
public List<PropertyDto> getProperties() {
return toPropertyDtos(repository.findAll());
}
public PropertyDetailsDto getPropertyById(Long id) {
return toPropertyDetailsDto(repository.findBy(id));
}
in some Mapper.java
...
public static List<PropertyDto> toPropertyDtos(List<Property> properties) {
return properties.stream()
.map(Mapper::toPropertyDto)
.collect(toList());
}
private static PropertyDto toPropertyDto(Property property) {
PropertyDto dto = new PropertyDto();
dto.propertyId = property.propertyId;
dto.propertyName = property.propertyName;
return dto;
}
// same stuff for `toPropertyDetailsDto`, you could extract common mapping parts in a separate private method inside `Mapper`
...
I finally found a way to override methods of Spring Data REST with a custom implementation. Unfortunately this disables the default handling.
My Repository should contain findAll and findById exposed over the GET: /games and GET: /games/{id} respectively and save should not be exported because it is overriden by the controller.
#RepositoryRestResource(path = "games", exported = true)
public interface GameRepository extends Repository<Game, UUID> {
Collection<Game> findAll();
Game findById(UUID id);
#RestResource(exported = false)
Game save(Game game);
}
My controller should handle POST: /games, generate the game on the server and return the saved Game.
#RepositoryRestController
#ExposesResourceFor(Game.class)
#RequestMapping("games")
public class CustomGameController {
private final GameService gameService;
public CustomGameController(GameService gameService) {
this.gameService = gameService;
}
#ResponseBody
#RequestMapping(value = "", method = RequestMethod.POST, produces = "application/hal+json")
public PersistentEntityResource generateNewGame(#RequestBody CreateGameDTO createGameDTO, PersistentEntityResourceAssembler assembler) {
Game game = gameService.generateNewGame(createGameDTO);
return assembler.toFullResource(game);
}
}
However when I try to GET: /games it returns 405: Method Not Allowed but POST: /games works as intended. When I change the value of the generateNewGame mapping to "new" all three requests work. But POST: /games/new is no RESTful URL Layout and I would rather avoid it. I don't understand why I get this behaviour and how I may solve it. Does anybody have a clue?
Use #BasePathAwareControllerannotation above your controller to preserve default spring data rest paths and add new custom path base on your need. Although overwrite default spring data rest path.
#BasePathAwareController
public class CustomGameController {
private final GameService gameService;
public CustomGameController(GameService gameService) {
this.gameService = gameService;
}
#ResponseBody
#RequestMapping(value = "", method = RequestMethod.POST, produces =
"application/hal+json")
public PersistentEntityResource generateNewGame(#RequestBody CreateGameDTO
createGameDTO, PersistentEntityResourceAssembler assembler) {
Game game = gameService.generateNewGame(createGameDTO);
return assembler.toFullResource(game);
}
}
Maybe you can do something we usually do in Linux. Set a fake path and link to it.
POST /games ==> [filter] request.uri.euqal("/games") && request.method==POST
==> Redirect /new/games
What you see also is /games.
Don't use /games/new, it may be conflict with things inner Spring.
Given a RESTful web service developed using the Spring Boot framework, I wanted a way to suppress the birthDate of all Users in the response. This is what I implemented after looking around for a solution :
#RestController
public class UserResource {
#Autowired
private UserDAOservice userDAOService;
#GetMapping("/users")
public MappingJacksonValue users() {
List<User> users = userDAOService.findAll();
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter
.filterOutAllExcept("id", "name");
FilterProvider filters = new SimpleFilterProvider().addFilter(
"UserBirthDateFilter", filter);
MappingJacksonValue mapping = new MappingJacksonValue(users);
mapping.setFilters(filters);
return mapping;
}
}
However, when I hit the rest end point in the browser, I can still see the birth date of the user in the response :
{
"id": 1,
"name": "Adam",
"birthDate": "1980-03-31T16:56:28.926+0000"
}
Question 1 : What API can I use to achieve my objective?
Next, assuming that I want to adhere to HATEOAS in combination with filtering, how can I go about doing this. I am unable to figure out the APIs that can be used for using these two features together :
#GetMapping("/users/{id}")
public EntityModel<User> users(#PathVariable Integer id) {
User user = userDAOService.findById(id);
if (user == null) {
throw new ResourceNotFoundException("id-" + id);
}
EntityModel<User> model = new EntityModel<>(user);
WebMvcLinkBuilder linkTo = linkTo(methodOn(this.getClass()).users());
model.add(linkTo.withRel("all-users"));
//how do I combine EntityModel with filtering?
return model;
}
Question 2 : How do I combine EntityModel with MappingJacksonValue?
Note : I am aware of #JsonIgnore annotation but that would apply the filter for all end points that use the domain; however, I want to restrict the filtering only to the two endpoints above.
Turns out for this to work, I have to add the #JsonFilter annotation above the DTO and provide the same name that was used while creating the SimpleFilterProvider.
#JsonFilter("UserBirthDateFilter")
public class User {
private Integer id;
#Size(min=2, message="user name must be atleast 2 characters")
#ApiModelProperty(notes="user name must be atleast 2 characters")
private String name;
#Past
#ApiModelProperty(notes="birth date cannot be in the past")
private Date birthDate;
//other methods
}
There is an easier way to do this, on your transfer object (the class you are sending back to the client), you can simply use the #JsonIgnore annotation to make sure the field is not serialized, and therefore sent to the client. So simply add #JsonIgnore inside your User class for your birthDay field.
You can also read more here about this approach:
https://www.baeldung.com/jackson-ignore-properties-on-serialization
If you need to return a different object for different endpoints (User without birthDay in your case, only for specific) you should create separate transfer objects and use those for their respective endpoints. You can pass your original entity (User) in the constructor to those classes and copy over all fields needed.
You can use Jackson's #JsonView feature. With this, you can tell a certain request mapping to produce serialized JSON with chosen set of properties.
public class View {
interface UserDetails {}
}
public class User {
#JsonView(View.UserDetails.class)
private Long id;
#JsonView(View.UserDetails.class)
private String name;
private String birthdate;
}
Controller be like
#JsonView(View.UserDetails.class)
#GetMapping("/users")
public MappingJacksonValue users() {
....
}
For question 2, I had the exact same question as you did, and here's what I did. It seems to be working:
#GetMapping(path = "/users/{id}")
public MappingJacksonValue retrieveUser(#PathVariable int id){
User user = service.findOne(id);
if(user==null){
throw new UserNotFoundException("id-"+id);
}
//"all-users", SERVER_PATH + "/users"
EntityModel<User> resource = EntityModel.of(user);
WebMvcLinkBuilder linkTo =
linkTo(methodOn(this.getClass()).retrieveAllUsers());
resource.add(linkTo.withRel("all-users"));
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter.filterOutAllExcept("id");
FilterProvider filters = new SimpleFilterProvider().addFilter("UserFilter",filter);
MappingJacksonValue mapping = new MappingJacksonValue(resource);
mapping.setFilters(filters);
return mapping;
}
Response for HTTP GET localhost:8080/users/1
{
"id": 1,
"links": [
{
"rel": "all-users",
"href": "http://localhost:8080/users"
}
]}
I'm using Spring Boot, Spring Data REST, Spring HATEOAS in my project.
My domain model is quite complex and I'd like to follow some of REST best practice as the fields selection.
I do know Spring projections but I'm looking for a way to tell what fields I need from the client at runtime. Simple as call GET /cars?fields=manufacturer,model,id,color.
Because I'd like to take advantage of Spring Data, I think I should create something in between the REST call and Spring.
Do you know some good resource/example to create something like that?
With Squiggly Filter
#GetMapping(value="cars")
public #ResponseBody List<Car> getCars(#RequestParam("fields") String fields){
List<Car> carList ;
-------
ObjectMapper mapper = = Squiggly.init(new ObjectMapper(), fields);
System.out.println(SquigglyUtils.stringify(mapper, carList));
-------
}
More on,
https://github.com/bohnman/squiggly-java
With SimpleBeanPropertyFilter,
Car class,
#JsonFilter("myfilter")
public class Car {
public String color;
public String model;
public String type;
At controller,
#GetMapping(value = "/cars")
public ResponseEntity<?> getCars(#RequestParam("fields") String fields) throws IOException {
List<Car> list = Arrays.asList(new Car("pink", "verna", "sedan"), new Car("black", "i10", "hatchback"),
new Car("voilet", "brizza", "SUV"));
SimpleFilterProvider filterProvider = new SimpleFilterProvider().addFilter("myfilter",
SimpleBeanPropertyFilter.filterOutAllExcept(fields.split(",")));
ObjectMapper mapper = new ObjectMapper().setFilterProvider(filterProvider);;
return new ResponseEntity<>(mapper.readValue(mapper.writeValueAsString(list),Object.class), HttpStatus.OK);
}