How to filter nested Map<String, Object> in Java?

Viewed 137

I have Map<String, Object> currencyRate that contains

"license": "https:/etc",
"timestamp": 1654,
"base": "USD",
"rates": {
"AED": 3.6,
"AFN": 88.9,
"ALL": 112,
 etc
}

And e.g. I want to get "AED" value which is 3.6.

I've tried this:

Map<String, Object> filtered = currencyRate.entrySet()
            .stream()
            .filter(map->map.getValue().toString().contains("AED"))
            .collect(Collectors.toMap(map -> map.getKey(), map -> map.getValue()));

But it returned this:

"rates": {
"AED": 3.6,
"AFN": 88.9,
"ALL": 112,
 etc
}

So "rates" is key, and it has values which also has keys and values.

How can I get keys and values in "rates"?

3 Answers

You don't need to use streams for that. Instead, you need to get the nested map by its key "rates" and type cast it into the appropriate type (note that this casting is unsafe):

double aedRate = ((Map<String, Double>) map.get("rates")).get("AED");

If you want to utilize streams for that at all costs, then again firstly you need to extract the nested map and convert it from the Object type into Map. Then create a stream over its entries and filter out the entry with the target key "AED":

Map.Entry<String, Double> result =
    ((Map<String, Double>) map.get("rates")).entrySet().stream()
        .filter(entry -> entry.getKey("AED"))
        .findFirst()
        .orElseThrow();

Note that using Object as generic type and operating via type casts isn't a good practice. The overall approach of a Map<String, Object> is error-prone and unmaintainable. You would never had this problem if you kept the data in your application structured appropriately.

Since you're using a map where each value can be any Object Map<String, Object> (there is no declaration so I'm just assuming here), I guess you could use the findFirst terminal operation and then cast the returned value to Map<String, Object> (I've used Object here too for the values since the data type is not specified in the question).

Of course, this makes sense only if you specifically want to use streams and under the strict assumption that there is only one value containing "AED" within your enclosing map (again, in the question is not specified if any other value can contain "AED"). Alternatively, you could simply perform a map.get("rates") to retrieve your nested map.

public static void main(String[] args) {
    Map<String, Object> map = new HashMap<>(Map.of(
            "license", "http:/etc",
            "timestamp", 1654,
            "base", "USD",
            "rates", Map.of(
                    "AED", 3.6,
                    "AFN", 88.9,
                    "ALL", 112
                    // ...
            )
    ));

    Map<String, Object> mapRes = (Map<String, Object>) map.entrySet()
            .stream()
            .filter(entry -> entry.getValue().toString().contains("AED"))
            .map(entry -> entry.getValue())
            .findFirst()
            .orElse(null);

    System.out.println(mapRes);
}

Output

{ALL=112, AED=3.6, AFN=88.9}

I guess You should use flatMap() to transform nested map to new stream.

Map<String, Object> filtered = currencyRate.entrySet()
            .stream()
            .filter(map->map.getValue().toString().contains("AED"))
            .flatMap(map->map.stream())
            .filter(...)
            .collect(Collectors.toMap(map -> map.getKey(), map -> map.getValue()));
Related