Автоматическая сериализация Rest контроллеров в CSV вместо JSON в Spring Boot - PullRequest
0 голосов
/ 06 ноября 2018

Я ищу быстрое / простое решение, как автоматически сериализовать выходные данные контроллеров 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);

В идеале я бы хотел, чтобы мои контроллеры могли возвращать выходные данные в формате JSON или CSV в зависимости от заголовка Accept в запросе.

1 Ответ

0 голосов
/ 07 ноября 2018

Мне удается добиться того, чего я хочу, путем:

  • определение пользовательского конвертера для приложения / csv
  • конвертер может записывать только CSV (он не поддерживает чтение)
  • преобразователь использует 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));
    }
}
...