How to extract the a value from a JSON object by matching the key in a Map using Java 8?

Viewed 2384

I'm using Java 8 and jackson to try and get an int value from a json object. Here is some similar code I used to verify the structure.

HashMap<String, Object> myMap = new HashMap<String, Object>();  
myMap
        .entrySet()
        .stream()
        .forEach(x -> System.out.println(x.getKey() + " => " + x.getValue()));

and the result:

myKey1 => {"number":1}

myKey2 => {"number":1}

myKey3 => {"number":2}

What I'm trying to do is use the key, like myKey1 to find the json value i.e. {"number":1} and pull out the actual number, which for that key is 1.

However, I don't all know the values of the key's, just the key I want to match up. So I have to do this dynamically. I know the structure of the values and that is always the same, except the number can be different.

I think because I don't know the keys and their order, plus I'm using entrySet, that is forcing me into using Optional, which is making this more difficult.

Here is my code where I'm actually using the key to pull the json value:

Optional<Object> object = myMap.entrySet().stream()
        .filter(e -> e.getKey().equals(myKey))
        .map(Map.Entry::getValue)
        .findFirst();

However, the stream pulls back

Optional[{"number":1}]

and I can't seem to get the number out so I can return it from a method. I don't actually have a Java class for the object, so I assume that is why it's returning Optional as I was getting a compile error without it, but I'm not sure.

Any ideas as to the right way to do this?

3 Answers

Why iterating over all the entries of the map and do a linear search by key, to get the value of the entry?

Just get the value directly:

Object value = myMap.get(myKey);

Now, with regard to the number inside value, as you say you don't have a class that represents the values of the map, the most likely thing is that the JSON library you're using is creating a Map for each value. So from now on, let's assume that the values are actually some implementation of Map:

Integer = null;

if (value != null) {
    // What's the type of the value? Maybe a Map?
    if (value instanceof Map) {
        Map<String, Object> valueAsMap = (Map<String, Object>) value;
        number = (Integer) valueAsMap.get("number");
    }
}

I'm assuming the numbers are Integers, but they can perfectly be Long instances or even Doubles or BigDecimals. Just be sure of the exact type, so the second cast doesn't fail.


EDIT: For completeness, here's the code for when the values are not maps, but of some class that represents a json node. Here I'm using JsonNode from Jackson library, but the approach is very similar for other libs:

Integer = null;

if (value != null) {
    if (value instanceof JsonNode) {
        JsonNode valueAsNode = (JsonNode) value;
        number = (Integer) valueAsNode.get("number").numberValue();
    }
}

findFirst() returns an Optional. You need to call .get() or .orElse() or .orElseThrow() after you call findFirst().

You can then cast the Object to JsonNode and retrieve the value. Here is the full code:-

public static void main(String[] args) throws Exception {
    ObjectMapper mapper = new ObjectMapper();
    HashMap<String, Object> myMap = new HashMap<String, Object>();
    myMap.put("myKey1", mapper.readTree("{\"number\":1}"));
    myMap.put("myKey2", mapper.readTree("{\"number\":1}"));
    myMap.put("myKey3", mapper.readTree("{\"number\":2}"));

    System.out.println(getNumberUsingMapKey(myMap, "myKey3"));
}

private static int getNumberUsingMapKey(Map<String, Object> map, String key) throws Exception {
    return Optional.of(map.get(key))
            .map(o -> ((JsonNode) o).get("number").asInt())
            .get(); //or use one of the following depending on your needs
//                .orElse(-1);
//                .orElseThrow(Exception::new);
}

//or use this method
private static int getNumberUsingMapKeyWithoutOptional(Map<String, Object> map, String key) throws Exception {
    Object o = map.get(key);
    return ((JsonNode) o).get("number").asInt();
}

Output

2

Unfortunately I didn't describe my problem statement well from the start, but this is the final working code that I was able to piece from the other answers.

This is what the data structure looked like. A key that had a value which was a JSON object that had a key and an int, in other words a nested JSON object.

Key: myKey1 Value:{"number":1}

Key: myKey2 Value:{"number":1}

Key: myKey3 Value:{"number":2}

I'm posting it in case some else runs into this use case. This may not be the best way to do it, but it works.

      Object value = myMap.get(keyName);
      ObjectMapper mapper = new ObjectMapper();
      String jsonString = (String) value;
      JsonNode rootNode = mapper.readTree(jsonString);    
      JsonNode numberNode = rootNode.path("number");
      System.out.println("number: " + numberNode.intValue());

and the result:

number: 1

Related