Parsing CSV responses with a custom RestTemplate HttpMessageConverter
source link: https://tech.asimio.net/2022/01/13/Parsing-CSV-responses-with-a-custom-RestTemplate-HttpMessageConverter.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
1. INTRODUCTION
Even though RestTemplate has been deprecated in favor of WebClient, it’s still a very popular choice to integrate Java applications with in-house or third-party services.
If you find yourself working on application modernization you would most-likely need to integrate with legacy systems. Don’t be surprised if you get HTML, plain text, or CSV responses when integrating with legacy systems.
Of course you could use RestTemplate to get the response as a String and covert it to a Java object. But that’s not how you do it when retrieving JSON or XML responses.
You would only need:
ResponseEntity<Film> result = this.restTemplate.getForEntity(uri, Film.class);
and RestTemplate’s default HttpMessageConverters take care of the conversion.
This blog post helps you to write a custom RestTemplate HttpMessageConverter to convert CVS responses to Java objects.
2. THE CODE
Let’s split the supporting Java code into subsections to better undertand each part.
2.1. DEPENDENCIES
If you are implementing RESTful applications using Spring Boot, spring-boot-starter-web
includes spring-web
which includes RestTemplate.
Let’s first bring in opencsv
dependency to parse CSV values into POJOs.
pom.xml
:
<properties>
<opencsv.version>5.5.2</opencsv.version>
</properties>
<dependency>
<groupId>com.opencsv</groupId>
<artifactId>opencsv</artifactId>
<version>${opencsv.version}</version>
</dependency>
2.2. OPENCSV PARSER
The RestTemplate instance will be parsing CSV content like:
"Airbus A319neo","31N","A19N"
"Airbus A320","320","A320"
"Airbus A320neo","32N","A20N"
"Airbus A321","321","A321"
"Airbus A321neo","32Q","A21N"
Let’s implement the CSV parser next.
CsvParser.java
:
public interface CsvParser<T> {
List<T> parse(InputStream input) throws IOException;
}
DefaultCsvParser.java
:
public class DefaultCsvParser<T> implements CsvParser<T> {
public DefaultCsvParser(Class<T> clazz) {
this.clazz = clazz;
}
@Override
public List<T> parse(InputStream input) throws IOException {
MappingStrategy<T> mappingStrategy = this.getMappingStrategy();
try (Reader reader = new InputStreamReader(input)) {
CsvToBean<T> csvToBean = new CsvToBeanBuilder<T>(reader)
.withType(this.clazz)
.withMappingStrategy(mappingStrategy)
.build();
return csvToBean.parse();
}
}
protected MappingStrategy<T> getMappingStrategy() {
MappingStrategy<T> result = new ColumnPositionMappingStrategy<>();
result.setType(this.clazz);
return result;
}
// ...
}
This implementation isn’t tied to any Web technology. You could use it in RESTful or Console applications.
The parse()
method converts the input to an instance of type this.clazz
. The mapping strategy used for this conversion is ColumnPositionMappingStrategy.
"Aerospatiale (Nord) 262","ND2","N262"
"Aerospatiale (Sud Aviation) Se.210 Caravelle","CRV","S210"
"Aerospatiale SN.601 Corvette","NDC","S601"
...
Field Position Airplane name 0 IATA Code 1 ICAO Code 2
2.3. CSV HttpMessageConverter
Let’s now implement a custom RestTemplate HttpMessageConverter to parse CSV content, CSV-formatted HTTP responses in this case.
CsvHttpMessageConverter.java
:
public class CsvHttpMessageConverter<T> extends AbstractGenericHttpMessageConverter<List<T>> {
private CsvParser<T> csvParser;
@Override
protected boolean supports(Class<?> clazz) {
return List.class.isAssignableFrom(clazz);
}
@Override
public List<T> read(Type type, Class<?> contextClass, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
return this.csvParser.parse(inputMessage.getBody());
}
@Override
protected List<T> readInternal(Class<? extends List<T>> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
return this.csvParser.parse(inputMessage.getBody());
}
// ...
}
A CsvHttpMessageConverter initial implementation extended from AbstractHttpMessageConverter, but requests resulted in:
UnknownContentTypeException: Could not extract response: no suitable HttpMessageConverter found for response type [java.util.List<com.asimio.demo.domain.Airplane>]
due to a combination of responseClass being null and there wasn’t found a messageConverter instance of GenericHttpMessageConverter.
CsvHttpMessageConverter needed to extend AbstractGenericHttpMessageConverter to get past this exception.
And all read()
and readInternal()
do is to execute parse()
.
Airplane.java
:
public class Airplane {
@CsvBindByPosition(position = 0)
private String name;
@CsvBindByPosition(position = 1)
private String iataCode;
@CsvBindByPosition(position = 2)
private String icaoCode;
// Getters, Setters
}
This is the POJO that is instantiated for each line in the CSV-formatted response. It uses opencsv
’s CsvBindByPosition annotation which matches the CSV parser mapping strategy discussed earlier.
2.4. RestTemplate CONFIGURATION
WebClientConfig.java
:
@Configuration
public class WebClientConfig {
@Bean
public CsvParser<Airplane> csvParser() {
return new DefaultCsvParser<>(Airplane.class);
}
@Bean
public RestTemplate restTemplate() {
RestTemplate result = new RestTemplate();
CsvHttpMessageConverter<Airplane> messageConverter = new CsvHttpMessageConverter<>(
this.csvParser(),
MediaType.ALL
);
result.getMessageConverters().add(messageConverter);
return result;
}
// ...
}
This Spring configuration class instantiates the Airplane CSV parser the CsvHttpMessageConverter uses.
And adds the custom CsvHttpMessageConverter to the default RestTemplate’s message converters list.
2.5. REST CONTROLLER
AirplaneController.java
:
@RestController
@RequestMapping(
value = "/api/airplanes",
produces = MediaType.APPLICATION_JSON_VALUE
)
public class AirplaneController {
private final RestTemplate restTemplate;
@GetMapping(path = "")
public ResponseEntity<List<Airplane>> findAirplanes() {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.TEXT_PLAIN));
HttpEntity<Void> request = new HttpEntity<>(headers);
ResponseEntity<List<Airplane>> airplanes = this.restTemplate.exchange(
"https://raw.githubusercontent.com/jpatokal/openflights/master/data/planes.dat",
HttpMethod.GET,
request,
new ParameterizedTypeReference<List<Airplane>>(){}
);
// Do something else with airplanes
return new ResponseEntity<>(airplanes.getBody(), HttpStatus.OK);
}
// ...
}
A simple RESTful endpoint implementation that retrieves CSV-formatted content from an external source, parses it, and answers back with a JSON-formatted response.
3. SAMPLE REQUEST
curl http://localhost:8080/api/airplanes | json_pp
[
{
"iataCode" : "ND2",
"icaoCode" : "N262",
"name" : "Aerospatiale (Nord) 262"
},
{
"name" : "Aerospatiale (Sud Aviation) Se.210 Caravelle",
"icaoCode" : "S210",
"iataCode" : "CRV"
},
{
"iataCode" : "NDC",
"name" : "Aerospatiale SN.601 Corvette",
"icaoCode" : "S601"
},
...
]
4. CONCLUSION
RestTemplate supports adding or overriding its default list of HttpMessageConverters.
This blog post helped you to implement a custom HttpMessageConverter to parse CSV content.
Thanks for reading and as always, feedback is very much appreciated. If you found this post helpful and would like to receive updates when content like this gets published, sign up to the newsletter.
5. SOURCE CODE
Accompanying source code for this blog post can be found at:
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK