Search code examples
javagenericsnested-generics

Avoiding unchecked casts of nested Maps in Java


I have a Java data structure which results from deserialising this JSON:

{
  'level1 value1': {
    'level2 value1': {
      'level3 value1': [ "25", "45", "78" ],
      // ...
      'level3 valueN': [ "59", "17", "42" ]
    },
    // ...
    'level2 valueN': {
      'level3 value1': [ "34", "89", "54" ],
      // ...
      'level3 valueN': [ "45", "23", "23" ]
    },
  },
  // ...
  'level1 valueN': {
    // ...
  }
}

In Java, this becomes:

Map<String, Map<String, Map<String, List<String>>>> data;

Of course, the number of levels is arbitrary, so I can't really nest collections in the variable declaration. What I'm doing instead is this:

void traverse(Map<String, ?> children) {
  for (Map.Entry<String, ?> node : data.entrySet()) {
    if (node.getValue() instanceof Map) {
      doSomethingWithNonLeafNode((Map<String, Map<String, ?>>) node);
    } else if (node.getValue() instanceof List) {
      doSomethingWithLeafNode((Map <String, List<String>>) node);
    }
  }
}


void doSomethingWithNonLeafNode(Map <String, Map<String ?>> node) {
  // do stuff
}


void doSomethingWithLeafNode(Map <String, List<String>> node) {
  // do stuff
}

This obviously a) uses some unchecked casts, and b) is ugly. I tried to define new types to get around this:

private interface Node extends Map<String, Map<String, ?>> {
}

private interface LeafNode extends Map<String, List<String>> {
}

// ...

    if (node.getValue() instanceof Map) {
      doSomethingWithNonLeafNode((Node) node);
    } else if (node.getValue() instanceof List) {
      doSomethingWithLeafNode((LeafNode) node);
    }

However, this gives me a runtime exception:

java.lang.ClassCastException: java.util.HashMap cannot be cast to com.foo.ReportDataProcessor$Node

How can I do this in a clean, warning-free fashion?


Solution

  • Defining new types doesn't help you because the JSON deserializer doesn't know about your Node & LeafNode interfaces, thus the concrete collection objects produced can't implement them.

    Since you don't actually know the element types of your maps in advance, there is no compile time type safety to be enforced: you can only rely on runtime type checks. Therefore generics just complicates your life and makes your code uglier, but doesn't bring you any benefit.

    So I think the least worst solution here would be to drop generics, using nongeneric Map and List types in your code.