Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make controller endpoint to get two different objects in java spring?

I have a server built with java and spring.

What i am trying to do is that my controller with the same endpoint will get two different objects.

This is an example for what I mean:

I know I can do that:

  public class Option1{
   private String name;
   ...
     //getter and setter
    }

public class Option2{
 private Long id;
  ...
//getter and setter
}

@Controller
public class Controller{

 @RequestMapping(value = "service/getData/option1", method = RequestMethod.POST)
 @ResponseBody
 public String searchProv(@ResponseBody Option1 data1){
  return "option1"
   }

@RequestMapping(value = "service/getData/option2", method = RequestMethod.POST)
@ResponseBody
public String searchProv(@ResponseBody Option2 data2){
  return "option2"
  }
}

but I wonder if it is possible to passing different json object to the same endpoint and do that:

 @Controller
public class Controller{

 @RequestMapping(value = "service/getData", method = RequestMethod.POST)
 @ResponseBody
 public ResponseEntity<Any> getData(@ResponseBody Option1And2 data){
if(data instanceof Option1){
  return return ResponseEntity<Any>(data.name,HttpStatus.OK)
}        
if(data instanceof Option2){
   return ResponseEntity<Any>(data.id,HttpStatus.OK)
}
 return ResponseEntity<Any>("ok",HttpStatus.OK)
  }

such that 'Option1And2' is generic object can be option1 or option2.

I tried to replace 'Option1And2' to 'Any' but it didn't went well because I get a list of keys and values

like image 374
JJ Redikes Avatar asked Nov 20 '18 15:11

JJ Redikes


People also ask

Can you have two request bodies?

You can add multiple body parameters to your path operation function, even though a request can only have a single body.

Can we have more than one controller in spring boot?

In Spring MVC, we can create multiple controllers at a time. It is required to map each controller class with @Controller annotation.

Which object is returned from controller in spring?

Instead, your @Controllers directly return Java objects, which Spring MVC will conveniently serialize to JSON/XML or any other format that the user requested with the help of HttpMessageConverters.

Can you have multiple rest controllers?

So what happens when you have two rest controller defined onto the same path? If you don't have any overlapping request mappings other than the code being slightly confusing, nothing will actually go wrong and you can successfully send requests to the methods inside each controller.


2 Answers

You should use JsonNode object.

for your example you should do this:

 @Controller
 public class Controller{

 @RequestMapping(value = "service/getData", method = RequestMethod.POST)
 @ResponseBody
 public ResponseEntity<Any> getData(@RequestBody JsonNode jsonNode){

   ObjectMapper obj = new ObjectMapper();

  if(jsonNode.has("name"){
   Option1 result= obj.convertValue(jsonNode,Option1.class)
  return ResponseEntity<Any>(result.name,HttpStatus.OK)
    }    

   else {

   Option2 result= obj.convertValue(jsonNode,Option2.class)
   return ResponseEntity<Any>(result.id,HttpStatus.OK)
    }

    return ResponseEntity<Any>("ok",HttpStatus.OK)
     }

the JsonNode and the ObjectMapper you should import from here:

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.JsonNode;

this link should help you to understand better on JsonNode and give you more details.

and this link should help you with the convertValue from JsonNode to java object(POJO).

like image 133
Tal Shani Avatar answered Nov 15 '22 00:11

Tal Shani


This is a good time to use inheritance and Java Generics. It is worth noting, if your controller has any dependencies such as a @Service or @Repository, then those too must be generic.

enter image description here

You might have a generic controller:

abstract class GenericController<T> {

    public abstract GenericService<T> getService();

    @GetMapping
    public ResponseEntity<Iterable<T>> findAll() {

        return ResponseEntity.ok(getService().findAll());
    }

    @PostMapping
    public ResponseEntity<T> save(T entity) {

        return ResponseEntity.ok(getService().save(entity));
    }

    // @DeleteMapping, @PutMapping
    // These mappings will automatically be inherited by
    // the child class. So in the case of findAll(), the API
    // will have a GET mapping on /category as well as a GET
    // mapping on /product. So, by defining and annotating the
    // CRUD operations in the parent class, they will automatically
    // become available in all child classes.
}

@Controller
@RequestMapping("/category")
class CategoryContr extends GenericController<Category> {

    @Autowired CategoryServ serv;

    @Override
    public GenericService<Category> getService() {
        return serv;
    }
}

@Controller
@RequestMapping("/product")
class ProductContr extends GenericController<Product> {

    @Autowired ProductServ serv;

    @Override
    public GenericService<Product> getService() {
        return serv;
    }
}

You then have to have abstract versions of the dependencies. The services:

abstract class GenericService<T> {

    public abstract GenericRepository<T> getRepository();

    public Iterable<T> findAll() {

        return getRepository().findAll();
    }

    public T save(T entity) {

        return getRepository().save(entity);
    }

}

@Service
class CategoryServ extends GenericService<Category> {

    @Autowired CategoryRepo repo;

    @Override
    public GenericRepository<Category> getRepository() {
        return repo;
    }
}

@Service
class ProductServ extends GenericService<Product> {

    @Autowired ProductRepo repo;

    @Override
    public GenericRepository<Product> getRepository() {
        return repo;
    }
}

Then, the services have their dependencies as well - the repositories:

@NoRepositoryBean
interface GenericRepository<T> extends JpaRepository<T, Long> {
}

@Repository
interface CategoryRepo extends GenericRepository<Category> {
}

@Repository
interface ProductRepo extends GenericRepository<Product> {
}

This was my first approach. It works very nicely. However, this does create a strong coupling between the business logic of each service and the generic service. The same holds true for the generic controller and its child classes. You can of course always override a particular CRUD operation. But, you must do this with care as you may created unexpected behavior. It is also worth noting that inheriting from classes that have methods that are annotated with @RequestMapping automatically exposes all of the annotated methods. This may be undesirable. For example, we may not want a delete option for categories, but we want it for products. To combat this, instead of annotating the method in the parent class, we can simply define it in the parent class, and override the desired CRUD operations with the added @RequestMapping annotation and then call the super class method.

Another approach is using annotations.

like image 28
Jabari Dash Avatar answered Nov 14 '22 23:11

Jabari Dash