Como processar objeto com mapa para saída CSV

Oct 22 2020

Tenho um conjunto de objetos abaixo, que preciso escrever em CSV:

public class OutputObject {
    private String userId;
    private Map<String, Object> behaviour;
}

O conjunto acima pode ter um mapa com dois, três ou quatro valores.

[
OutputObject1 [userId=11, behaviours={color=white, size=S, owner=Mr. A}], 
OutputObject2 [userId=22, behaviours={color=black, isNew=true}],
OutputObject3 [userId=33, behaviours={color=green, size=L}]
]

Saída de CSV necessária:

userId, color, size, owner, isNew
11,     white,  S,   Mr. A,
22,     black,   ,        , true
33,     green,  L,        ,

Comecei com o snippet abaixo para imprimir:

     // Set<OutputObject> outputObjectSet already received.
     JSONArray jsonArrayObject = new JSONArray(outputObjectSet);
     String csvValue = CDL.toString(jsonArrayObject);
     FileWriter fileWriter = new FileWriter(fileObject, true);
     fileWriter.write(csvValue);
     fileWriter.close();

Mas acima está criando um csv de duas colunas com userId e comportamentos de impressão de todos os comportamentos do objeto de mapa . Como obter o tipo de saída acima.

Como o conjunto pode conter um grande número de tais objetos, como isso pode ser feito de forma eficiente.

Respostas

1 AlexRudenko Oct 22 2020 at 22:43

Usar JSONArray parece ser redundante aqui, você pode implementar um método auxiliar para serializar OutputObjectem uma string CSV, levando em consideração que a ordem das colunas precisa ser mantida:

public class CSVSerializer {
    public static String transform(OutputObject obj) {
        String[] fields = {"color", "size", "owner", "isNew"};
        return Stream.concat(
                Stream.of(obj.getUserId()), 
                Arrays.stream(fields)
                      .map(f -> obj.getBehaviour().get(f))
                      .map(v -> v == null ? "" : v.toString()) 
               )
               .collect(Collectors.joining(","));
    }
}


String csv = outputObjectSet.stream()
                            .map(CSVSerializer::transform)
                            .collect(Collectors.joining("\n"));
// print csv contents
1 sb27 Oct 22 2020 at 22:23

Isso deve funcionar de forma bastante eficiente, mesmo com um número maior de objetos, uma vez que Java HashMape Arrays.sort()implementações são muito rápidas. Observe que essa implementação depende da common-text-library do Apache para escapar do conteúdo.

private static void outputCSV(List<OutputObject> objects, PrintStream output) {
    AtomicInteger highestBehaviourIndex = new AtomicInteger();

    HashMap<String, Integer> behaviourIndexMap = new HashMap<>();

    // Give every behaviour an index
    for (OutputObject object : objects) {
        object.getBehaviour().forEach((name, value) -> behaviourIndexMap.computeIfAbsent(name, (unused) -> highestBehaviourIndex.getAndIncrement()));
    }

    String[] behaviours = new String[highestBehaviourIndex.get()];

    behaviourIndexMap.forEach((name, index) -> {
        behaviours[index] = name;
    });

    output.println("userId, " + String.join(", ", behaviours));

    // Sort by ID
    objects.sort(Comparator.comparingInt(OutputObject::getUserId));

    for (OutputObject object : objects) {
        // Print line
        StringJoiner joiner = new StringJoiner(", ");
        
        for (String behaviour : behaviours) {
            joiner.add(StringEscapeUtils.escapeCsv(object.getBehaviour().getOrDefault(behaviour, "").toString()));
        }
        
        output.println(object.getUserId() + ", " + joiner.toString());
    }

}