How can I transform a Map with a value of Object to the appropriate type?

Viewed 125

I am working on a project where I need to accept a Map called properties that is of type Map<String, Object>. There are going to be potentially many different keys in this Map, but I only care about one: xpath. An xpath can have one of three different types of values:

  1. A string, such as {"xpath": "path/to/xml/tag"}
  2. A List of xpaths, such as: {"xpath": ["path/to/xml/tag1", "tag2", "path/tag3"}
  3. A Map<String, Map<String, Boolean>>, such as:
{
  "xpath":
  {
    "path/to/xml":
    {
      "setting1?": true,
      "setting2?": true
    },
    "path/tag2":
    {
      "setting1?": false,
      "setting2": true
    },
    "path/to/tag3": null
  }
}

Now I have three variables: String xpath, Set<String> xpaths, Map<String, Map<String, boolean> xpathMap. I have a function that is supposed to try and map the values of the "xpath" key in the properties map, and it looks like this:

private void decideXPathType(Map<String, Object> properties)
    {
        Object propertiesXPath = properties.get("xpath");

        if (propertiesXPath instanceof String)
        {
            this.xpath = (String) propertiesXPath;
        } else if (propertiesXPath instanceof List)
        {
            this.xpaths = new HashSet<String>((List) propertiesXPath);
        } else if (propertiesXPath instanceof Map)
        {
            for (Object key : ((Map) propertiesXPath).keySet())
            {
                Map<String, Boolean> value = (Map<String, Boolean>) ((Map) propertiesXPath).get(key);
                this.xpathMap.put((String) key, value);
            }
        } else
        {
            throw new IllegalArgumentException("the xpath value is neither String, List, or Map<String, Boolean>");
        }
    }

But this function looks so bad - there is so much casting, etc - and although it works, it just looks too messy, and I imagine something can go wrong... any ideas on how I can make this cleaner?

Edit: Some more details

The properties map is originally a json JsonNode requestBody that I receive from a service. Using ObjectMapper, I create a properties map as such:

Map<String, Object> properties = new ObjectMapper().convertValue(new ObjectMapper().readTree(requestBody), new TypeReference<Map<String, Object>>(){});

If I receive a json string that is the value of the xpathMap example that I gave, I get something that looks like this: enter image description here

Hope this information helps?

1 Answers

In your JSON, use different keys for these different types of values: String, List and Map. Deserializing a map:

@Test
public void test0() throws IOException {
    ObjectMapper om = new ObjectMapper();

    InputStream inputStream = getClass().getClassLoader().getResourceAsStream("xpath-object.json");
    JsonNode jsonNode = om.readTree(inputStream);

    Map<String, Map<String, Boolean>> value = om.readValue(jsonNode.get("xpath").toString(), Map.class);
    // prints {path/to/xml={setting1?=true, setting2?=true}, path/to/tag3=null, path/tag2={setting1?=false, setting2=true}}
    System.out.println(value);
}

If you need to work with 3rd party JSON, you can use following approach:

@Test
public void test() throws IOException {
    testThemAll("xpath-scalar.json");
    testThemAll("xpath-array.json");
    testThemAll("xpath-object.json");
    // prints: 
    // path/to/xml/tag
    // [path/to/xml/tag1, tag2, path/tag3]
    // {path/to/xml={setting1?=true, setting2?=true}, path/to/tag3=null, path/tag2={setting1?=false, setting2=true}}
}

private void testThemAll(String fileName) throws IOException {
    ObjectMapper om = new ObjectMapper();

    InputStream inputStream = getClass().getClassLoader().getResourceAsStream(fileName);
    JsonNode jsonNode = om.readTree(inputStream).get("xpath");

    if (jsonNode.isValueNode())
        System.out.println(jsonNode.asText());
    else if (jsonNode.isArray()) {
        System.out.println(om.readValue(jsonNode.toString(), List.class));
    } else if (jsonNode.isObject()) {
        Map<String, Map<String, Boolean>> value = om.readValue(jsonNode.toString(), Map.class);
        System.out.println(value);
    }
}
Related