Martijn Burger Martijn Burger - 2 months ago 9
Java Question

How to convert a table to a map of maps

I have a CSV file looking something like this:

request.car.model (STRING),request.car.make (STRING),request.person.lastname (STRING),other.person.birthdate (DATE),other.person.length (INTEGER)
BMW,7 series,Doe,31/12/1980,170
Tesla,Model S,Smith,1/1/1975,172
Volvo,C40,Johnson,13/11/1982,189


The first line is header, with the type that is parsed between parenthesis. The header uses a dot notation, very much like Javascript would parse a complex object.

What I need to do is create a Java
Map<String, Serializable>
for every line in the CSV file. For instance the first line needs to result in a
map
looking like this in Java:

Map<String, Serializable> requestCarMap = new HashMap<>();
requestCarMap.put("make", "BMW");
requestCarMap.put("model", "7 series");
Map<String, Serializable> requestPersonMap = new HashMap<>();
requestPersonMap.put("lastName", "Doe");
Map<String, Serializable> requestMap = new HashMap<>();
requestMap.put("car", (HashMap) requestCarMap);
requestMap.put("pesron", (HashMap) requestPersonMap);
Map<String, Serializable> otherPersonMap = new HashMap<>();
otherPersonMap.put("birthdate", new Date(1980,12,31));
Map<String, Serializable> otherMap = new HashMap<>();
otherMap.put("person", (HashMap) otherPersonMap);
Map<String, Serializable> map = new HashMap<>();
map.put("request", (HashMap) requestMap);
map.put("other", (HashMap) otherMap);


I currently have a method that creates a map based on the values, however, without splitting the header and putting it in SubMaps:

protected static Map<String, Serializable> parseRecord(CSVRecord record, CSVRecord header) {
final Map<String, Serializable> map = new HashMap<>();
for (int i = 0; i < record.size(); i++) {
String headerField = getHeader(header.get(i));
String[] headerFieldPart = headerField.split("\\.");
String headerFieldType = getHeaderFieldType(header.get(i));
Object recordField = getRecordField(headerFieldType, record.get(i));
if (recordField != null) {
map.put(headerField, (Serializable) recordField);
} else {
log.warn("Skipped value for record item: "
+ record.get(i));
}
}
return map;
}


How can I put the values in subMaps as in the example in a generic way? I do not know the names of the columns or how deep the maps are nested upfront. I just cannot figure out the logic that works in case the subMap does not exist yet, exists, etc. Any smart solutions? Any libraries that will help me with this problem?

mtj mtj
Answer

Basically, split the headers at the dots, iterate over the parts, and switch a target map pointer according to the relevant part.

Like this (assuming that you got the CSV-thing down):

public static void main(String[] args) {
    String[] headers = { "request.car.model", "request.car.make", "request.buyer" }; 
    String[] values = { "a", "b", "c" };

    Map<String, Serializable> outer = new HashMap<>();

    for(int i = 0; i < headers.length; i++) {
        String header = headers[i];
        String value  = values[i];

        String[] parts = header.split("\\.");
        Map<String, Serializable> targetMap = outer;
        for(int j = 0; j < parts.length - 1; j++)
            targetMap = (Map<String, Serializable>) targetMap.computeIfAbsent(parts[j], x -> new HashMap<>());
        targetMap.put(parts[parts.length - 1], value);
    }

    System.out.println(outer.get("request"));
}

Note, that this will only work if the headers are consistent, i.e. there is no such thing as a value for "a.a" followed by a value for "a.a.b".