как прочитать csv во вложенный json с помощью jackson java

у меня есть этот тип csv:

metric,value,date
temp_a,622.0,1477895624866
temp_a,-3.0,1477916224866
temp_a,365.0,1477917224866
temp_b,861.0,1477895624866
temp_b,767.0,1477917224866

и я хочу использовать java jackson для преобразования его в json, но не в json; это должно быть так:

[
  {
    "metric":"temp_a",
    "datapoints":[
      [622, 1477895624866],
      [-3, 1477916224866],
      [365, 1477917224866]
    ]
  },
  {
    "metric":"temp_b",
    "datapoints":[
      [861, 1477895624866],
      [767, 1477917224866]
    ]
  }
]

где dataponits — это массив, содержащий значение и дату в csv.

мне удалось использовать Джексона, чтобы получить этот результат:

{metric=temp_a, value=622.0, date=1477895624866}
{metric=temp_a, value=-3.0, date=1477916224866}
{metric=temp_a, value=365.0, date=1477917224866}
{metric=temp_b, value=861.0, date=1477895624866}
{metric=temp_b, value=767.0, date=1477917224866}

но это не то, что я хочу, и мне немного сложно понять и поиграть с документом Джексона, может быть, это возможно с Pojos или аннотациями, но я не могу их понять, я не мог найти, как сделать вложенный json.

Если я могу сделать это лучше, это что-то другое, тогда Джексон, пожалуйста, скажи мне. Спасибо за помощь.


person feiz    schedule 22.04.2020    source источник


Ответы (1)


Вам не нужно всегда десериализовать CSV в структуру POJO и реализовывать собственные сериализаторы. В этом случае вы также можете:

  • Десериализовать CSV в Map
  • Сгруппировать по элементам в Map форме metric -> [[...], [...]]
  • Преобразование выше Map в другую форму Map
  • Серийно преобразовать Map в JSON

Пример кода может выглядеть следующим образом:

import com.fasterxml.jackson.core.util.DefaultIndenter;
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import com.fasterxml.jackson.databind.MappingIterator;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.dataformat.csv.CsvMapper;
import com.fasterxml.jackson.dataformat.csv.CsvSchema;

import java.io.File;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

public class CsvApp {

    public static void main(String[] args) throws Exception {
        File csvFile = new File("./resource/test.csv").getAbsoluteFile();

        CsvMapper csvMapper = CsvMapper.builder().build();
        MappingIterator<Map> rows = csvMapper
                .readerWithSchemaFor(Map.class)
                .with(CsvSchema.emptySchema().withHeader())
                .readValues(csvFile);

        DataConverter converter = new DataConverter();
        List<Map<String, Object>> result = converter.toMetricDataPoints(rows);

        ObjectMapper jsonMapper = JsonMapper.builder()
                .enable(SerializationFeature.INDENT_OUTPUT)
                .build();

        jsonMapper.writeValue(System.out, result);
    }

}

class DataConverter {

    public List<Map<String, Object>> toMetricDataPoints(MappingIterator<Map> rows) {
        return toStream(rows)
            //group by metric -> [value, date]
            .collect(Collectors.groupingBy(map -> map.get("metric"),
                Collectors.mapping(map -> Arrays.asList(toNumber(map.get("value")), toNumber(map.get("date"))),
                    Collectors.toList())))
            .entrySet().stream()
            // convert to Map: metric + datapoints
            .map(entry -> {
                Map<String, Object> res = new LinkedHashMap<>(4);
                res.put("metric", entry.getKey());
                res.put("datapoints", entry.getValue());

                return res;
            }).collect(Collectors.toList());
    }

    private Stream<Map> toStream(MappingIterator<Map> rowIterator) {
        return StreamSupport.stream(Spliterators.spliteratorUnknownSize(rowIterator, Spliterator.ORDERED), false);
    }

    private long toNumber(Object value) {
        return new BigDecimal(Objects.toString(value, "0")).longValue();
    }
}

Над кодом печатается:

[ {
  "metric" : "temp_a",
  "datapoints" : [ [ 622, 1477895624866 ], [ -3, 1477916224866 ], [ 365, 1477917224866 ] ]
}, {
  "metric" : "temp_b",
  "datapoints" : [ [ 861, 1477895624866 ], [ 767, 1477917224866 ] ]
} ]

Как видите, мы использовали только базовую функциональность Jackson, остальные манипуляции с данными мы реализовали с помощью Java 8 API.

Смотрите также:

  1. Прямое преобразование файла CSV в файл JSON с помощью библиотека
  2. Как преобразовать итератор в поток?
  3. Десериализация Jackson JSON: элементы массива в каждой строке
person Michał Ziober    schedule 24.04.2020
comment
Большое спасибо за вашу помощь, это именно то, что я ищу :), но я просто хочу знать, будет ли это нормально работать с большими файлами csv? это лучший способ с большими файлами csv? - person feiz; 25.04.2020
comment
@feiz, это зависит от того, что вы подразумеваете под large file. Если файл меньше нескольких мегабайт, приведенный выше код должен работать. Приведенный выше код загружает все данные в память и выполняет операции в памяти. Итак, пока вы не получите OutOfMemoryError, вы можете использовать вышеуказанный тест. В противном случае вам нужно узнать об API потоковой передачи. - person Michał Ziober; 25.04.2020
comment
хорошо, я вижу, в этом случае я думаю, что этот код будет работать нормально, большое спасибо :) - person feiz; 25.04.2020