samba
samba

Reputation: 3091

Scala - How to handle key not found in a Map when need to skip non-existing keys without defaults?

I have a set of Strings and using it as key values to get JValues from a Map:

val keys: Set[String] = Set("Metric_1", "Metric_2", "Metric_3", "Metric_4")
val logData: Map[String, JValue] = Map("Metric_1" -> JInt(0), "Metric_2" -> JInt(1), "Metric_3" -> null)

In the below method I'm parsing values for each metric. First getting all values, then filtering to get rid of null values and then transforming existing values to booleans.

val metricsMap: Map[String, Boolean] = keys
      .map(k => k -> logData(k).extractOpt[Int]).toMap
      .filter(_._2.isDefined)
      .collect {
        case (str, Some(0)) => str -> false
        case (str, Some(1)) => str -> true
      }

I've faced a problem when one of the keys is not found in the logData Map. So I'm geting a java.util.NoSuchElementException: key not found: Metric_4.

Here I'm using extractOpt to extract a value from a JSON and don't need default values. So probably extractOrElse will not be helpful since I only need to get values for existing keys and skip non-existing keys.

What could be a correct approach to handle a case when a key is not present in the logData Map?

UPD: I've achieved the desired result by .map(k => k -> apiData.getOrElse(k, null).extractOpt[Int]).toMap. However still not sure that it's the best approach.

Upvotes: 0

Views: 1326

Answers (1)

hoyland
hoyland

Reputation: 1824

That the values are JSON is a red herring--it's the missing key that's throwing the exception. There's a method called get which retrieves a value from a map wrapped in an Option. If we use Ints as the values we have:

val logData = Map("Metric_1" -> 1, "Metric_2" -> 0, "Metric_3" -> null)
keys.flatMap(k => logData.get(k).map(k -> _)).toMap

> Map(Metric_1 -> 1, Metric_2 -> 0, Metric_3 -> null)

Using flatMap instead of map means unwrap the Some results and drop the Nones. Now, if we go back to your actual example, we have another layer and that flatMap will eliminate the Metric_3 -> null item:

keys.flatMap(k => logData.get(k).flatMap(_.extractOpt[Int]).map(k -> _)).toMap

You can also rewrite this using a for comprehension:

(for {
   k <- keys
   jv <- logData.get(k)
   v <- jv.extractOpt[Int]
 } yield k -> v).toMap

I used Success and Failure in place of the JSON values to avoid having to set up a shell with json4s to make an example:

val logData = Map("Metric_1" -> Success(1), "Metric_2" -> Success(0), "Metric_3" -> Failure(new RuntimeException()))
scala> for {
 | k <- keys
 | v <- logData.get(k)
 | r <- v.toOption
 | } yield k -> r
 res2: scala.collection.immutable.Set[(String, Int)] = Set((Metric_1,1), (Metric_2,0))

Upvotes: 5

Related Questions