CodeMaster
CodeMaster

Reputation: 171

How should I design my restfull API to accept data via POST in different formats (JSON,CSV) for the same purpose?

I have a business requirement to develop a restfull API using Spring boot which does the following :-

a) Accept the vehicle data in csv format over a POST request from the client. b) Accept the vehicle data in JSON format over a POST request from the client.

In the above a) and b) the fields are same but just in different formats ( One is JSON and another is CSV ).

My question is how should be my design to chieve this?

1) Shall I simply go ahead by creating class A and have 2 different endpoint methods. One to accept csv and another to accept json? Or there are any better ways to deal with such scenarios?

2) What should my class structure look like?

3) Any specific design patterns that suit this requirement? Or any specific reccomendation to deal with such scenario?

Any help is highly appreciated.

Upvotes: 2

Views: 718

Answers (1)

expandable
expandable

Reputation: 2300

You can do this by defining a Deserializer interface and different implementations for each format.

To question 1: It depends on how you want to do this. One way is to use a single endpoint and use the Content-Type header of the Web Request and choose a Deserializer based on it.

Here's an example:

public enum Formats { JSON, CSV, XML }

public interface Deserializer {

  Format getFormat();
  object deserialize(string input);
}

public JSONDeserializer extends Deserializer {

    public Format getFormat() { return Formats.JSON; }
    public object deserialize(string input) { .... }
}

public CSVDeserializer extends Deserializer {
    public Format getFormat() { return Formats.CSV; }
    public object deserialize(string input) { .... }
}

public XMLDeserializer extends Deserializer {
    public Format getFormat() { return Formats.XML; }
    public object deserialize(string input) { .... }
}

If you want to avoid having explicit Format enum to define your types you can use the content-type directly as string or MediaType. This will cause your Deserializer interface to be tightly coupled to your request handlers thus making it hard to use them elsewhere in the code. Also as an alternative design you can move the responsibility of the check in the Deserializer.

public interface Deserializer {

  string getContentType();
  object deserialize(string input);
}

public JSONDeserializer extends Deserializer {

    public string getContentType() {
        return "application/json";
    }

    public object deserialize(string input) { .... }
}

public interface Deserializer {

  bool cetDeserialize(string contentType);
  object deserialize(string input);
}

public JSONDeserializer extends Deserializer {

    public bool cetDeserialize(string contentType) {
       return contentType == "application/json";
    }

    public object deserialize(string input) { .... }
}

Here's how you use it.

List<String> mDeserializers = Arrays.asList(
    new JSONDeserializer(), 
    new CSVDeserializer(), 
    new XMLNDeserializer()
);

Deserializer deserializer = mDeserializers
      .stream()
      .filter(d => d.getContentType() == contentType)
      .findFirst();

// or if you use a specific format:
Deserializer deserializer = mDeserializers
      .stream()
      .filter(d => d.getFormat() == getFormatFromContentType(contentType))
      .findFirst();

deserializer.deserialize(input);

As for the spring, havent tried it myself, you can do something like this without specifying consumes:

 @RequestMapping(value = "/data", method = RequestMethod.POST)
 public String process(
   @RequestHeader HttpHeaders httpHeaders, 
   @RequestBody String body){
   // get value of content-type header as string
   // get serializer based on string value
 }

or

 @RequestMapping(value = "/data", method = RequestMethod.POST)
 public String process(
   @RequestHeader(value="Content-Type") String contentType, 
   @RequestBody String body){
   // get serializer based on string value
 }

Upvotes: 1

Related Questions