Cómo procesar objetos con mapa para salida CSV

Oct 22 2020

Tengo un conjunto de objetos a continuación, que necesito escribir en CSV:

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

El conjunto anterior puede tener un mapa con dos, tres o cuatro 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}]
]

Salida de CSV requerida:

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

Comencé con el siguiente fragmento 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();

Pero arriba está la creación de un csv de dos columnas con userId y comportamientos que imprimen todos los comportamientos del objeto del mapa . Cómo lograr el tipo de salida anterior.

Dado que el conjunto puede contener una gran cantidad de tales objetos, ¿cómo se puede hacer de manera eficiente?

Respuestas

1 AlexRudenko Oct 22 2020 at 22:43

El uso de JSONArray parece ser redundante aquí, podría implementar un método auxiliar para serializar OutputObjecten una cadena CSV, teniendo en cuenta que el orden de las columnas debe mantenerse:

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

Esto debería funcionar bastante eficiente, incluso con un mayor número de objetos desde Java HashMapy Arrays.sort()las implementaciones son bastante rápido. Tenga en cuenta que esta implementación se basa en la common-textbiblioteca de Apache para escapar del contenido.

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());
    }

}