在 Spring Boot 中自动将 Rest 控制器序列化为 CSV 而不是 JSON
Automatically serialize Rest controllers to CSV instead of JSON in Spring Boot
我正在寻找 quick/easy 解决方案如何自动将 Rest 控制器输出序列化为 CSV 而不是 JSON。我有最简单的 Spring 启动应用程序:
@SpringBootApplication
public class CsvExportApplication {
public static void main(String[] args) {
SpringApplication.run(CsvExportApplication.class, args);
}
}
class User {
String name;
String surname;
public User(String name, String surname) {
this.name = name;
this.surname = surname;
}
public void setName(String name) {
this.name = name;
}
public void setSurname(String surname) {
this.surname = surname;
}
public String getName() {
return name;
}
public String getSurname() {
return surname;
}
}
@RestController
class UserController {
@GetMapping(value = "/users")
List<User> list() {
return Arrays.asList(new User("adam", "kowalsky"), new User("john", "smith"));
}
}
我使用了 jackson-dataformat-csv
并提出了以下将 List<User>
序列化为 String
的代码,但理想情况下我不想更改其余控制器代码:
CsvMapper mapper = new CsvMapper();
CsvSchema schema = mapper.schemaFor(User.class).withHeader();
mapper.writerFor(List.class).with(schema).writeValueAsString(users);
理想情况下,我希望我的控制器能够 return 输出 JSON 或 CSV,具体取决于请求中的接受 header。
我通过以下方式实现了我想要的:
- 正在为 application/csv
定义自定义转换器
- 转换器只能写入CSV(不支持读取)
- 转换器使用 Jackon 的
ObjectMapper
(以确保 CSV 和 JSON 输出使用相同的日期格式)
- 转换器即时构建
jackson-dateformat-csv
模式,因为当前不支持无模式写入:https://github.com/FasterXML/jackson-dataformats-text/issues/114
代码:
class CsvConverter<T> extends AbstractHttpMessageConverter<T> {
private final ObjectMapper objectMapper;
CsvConverter(ObjectMapper objectMapper) {
super(new MediaType("application", "csv"));
this.objectMapper = objectMapper;
}
@Override
protected boolean supports(Class<?> clazz) {
return true;
}
@Override
protected T readInternal(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
return null;
}
@Override
protected void writeInternal(T object, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
try {
ObjectWriter objectWriter = getCsvWriter(object);
try (PrintWriter outputWriter = new PrintWriter(outputMessage.getBody())) {
outputWriter.write(objectWriter.writeValueAsString(object));
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
ObjectWriter getCsvWriter(T object) {
Set<String> fields = getUniqueFieldNames(object);
CsvSchema.Builder schemaBuilder = CsvSchema.builder().setUseHeader(true);
for (String field : fields) {
schemaBuilder.addColumn(field);
}
return new CsvMapper().writerFor(List.class).with(schemaBuilder.build());
}
Set<String> getUniqueFieldNames(T object) {
try {
JsonNode root = objectMapper.readTree(objectMapper.writeValueAsString(object));
Set<String> uniqueFieldNames = new LinkedHashSet<>();
root.forEach(element -> {
Iterator<String> it = element.fieldNames();
while (it.hasNext()) {
String field = it.next();
uniqueFieldNames.add(field);
}
});
return uniqueFieldNames;
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}
@Configuration
class AppConfig implements WebMvcConfigurer {
private final ObjectMapper objectMapper;
AppConfig(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new CsvConverter<>(objectMapper));
}
}
我正在寻找 quick/easy 解决方案如何自动将 Rest 控制器输出序列化为 CSV 而不是 JSON。我有最简单的 Spring 启动应用程序:
@SpringBootApplication
public class CsvExportApplication {
public static void main(String[] args) {
SpringApplication.run(CsvExportApplication.class, args);
}
}
class User {
String name;
String surname;
public User(String name, String surname) {
this.name = name;
this.surname = surname;
}
public void setName(String name) {
this.name = name;
}
public void setSurname(String surname) {
this.surname = surname;
}
public String getName() {
return name;
}
public String getSurname() {
return surname;
}
}
@RestController
class UserController {
@GetMapping(value = "/users")
List<User> list() {
return Arrays.asList(new User("adam", "kowalsky"), new User("john", "smith"));
}
}
我使用了 jackson-dataformat-csv
并提出了以下将 List<User>
序列化为 String
的代码,但理想情况下我不想更改其余控制器代码:
CsvMapper mapper = new CsvMapper();
CsvSchema schema = mapper.schemaFor(User.class).withHeader();
mapper.writerFor(List.class).with(schema).writeValueAsString(users);
理想情况下,我希望我的控制器能够 return 输出 JSON 或 CSV,具体取决于请求中的接受 header。
我通过以下方式实现了我想要的:
- 正在为 application/csv 定义自定义转换器
- 转换器只能写入CSV(不支持读取)
- 转换器使用 Jackon 的
ObjectMapper
(以确保 CSV 和 JSON 输出使用相同的日期格式) - 转换器即时构建
jackson-dateformat-csv
模式,因为当前不支持无模式写入:https://github.com/FasterXML/jackson-dataformats-text/issues/114
代码:
class CsvConverter<T> extends AbstractHttpMessageConverter<T> {
private final ObjectMapper objectMapper;
CsvConverter(ObjectMapper objectMapper) {
super(new MediaType("application", "csv"));
this.objectMapper = objectMapper;
}
@Override
protected boolean supports(Class<?> clazz) {
return true;
}
@Override
protected T readInternal(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
return null;
}
@Override
protected void writeInternal(T object, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
try {
ObjectWriter objectWriter = getCsvWriter(object);
try (PrintWriter outputWriter = new PrintWriter(outputMessage.getBody())) {
outputWriter.write(objectWriter.writeValueAsString(object));
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
ObjectWriter getCsvWriter(T object) {
Set<String> fields = getUniqueFieldNames(object);
CsvSchema.Builder schemaBuilder = CsvSchema.builder().setUseHeader(true);
for (String field : fields) {
schemaBuilder.addColumn(field);
}
return new CsvMapper().writerFor(List.class).with(schemaBuilder.build());
}
Set<String> getUniqueFieldNames(T object) {
try {
JsonNode root = objectMapper.readTree(objectMapper.writeValueAsString(object));
Set<String> uniqueFieldNames = new LinkedHashSet<>();
root.forEach(element -> {
Iterator<String> it = element.fieldNames();
while (it.hasNext()) {
String field = it.next();
uniqueFieldNames.add(field);
}
});
return uniqueFieldNames;
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}
@Configuration
class AppConfig implements WebMvcConfigurer {
private final ObjectMapper objectMapper;
AppConfig(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new CsvConverter<>(objectMapper));
}
}