Karan Ashar
Karan Ashar

Reputation: 1400

How do I access "filtered" items from collection?

I have a string val trackingHeader = "k1=v1, k2=v2, k3=v3, k4=v4" which I would like to parse and convert it to a Map(k1 -> v1, k2 -> v2, k3 -> v3, k4 -> v4). Following is the code I used to do this:

val trackingHeadersMap = trackingHeader
  .replaceAll("\\s", "")
  .split(",")
  .map(_ split "=")
  .map { case Array(k, v) => (k, v) }
  .toMap

I was able to get my desired output. But, I also need to handle a malformed input case like val trackingHeader = "k1=v1, k2=v2, k3=v3, k4=". Notice there is no value for key k4. My above code will start breaking with scala.MatchError: [Ljava.lang.String;@622a1e0c (of class [Ljava.lang.String;) so I changed it to:

val trackingHeadersMap = trackingHeader
  .replaceAll("\\s", "")
  .split(",")
  .map(_ split "=")
  .collect { case Array(k, v) => (k, v) }
  .toMap

Great now I have handle the malformed case as well by using collect but I would like to know what key had this issue and log it (in this example its k4). I tried the following and was able to get the desired result but I am not sure if its the right way to do it:

val badKeys = trackingHeader
    .replaceAll("\\s", "")
    .split(",")
    .map(_ split "=")
    .filterNot(_.length == 2)

Now I can iterate over the badKeys and print them out. Is there a better way to do this?

Upvotes: 1

Views: 58

Answers (2)

Dima
Dima

Reputation: 40500

You could make the result optional, and use flatMap instead of map

 .flatMap {
    case Array(k, v) => Some(k -> v)
    case Array(k) => println(s"Bad entry: $k"); None
  }

Upvotes: 2

Tzach Zohar
Tzach Zohar

Reputation: 37832

One solution would be to add a map step that prints the bad key for elements matching a one-element array before the call to collect:

val trackingHeadersMap = trackingHeader
  .replaceAll("\\s", "")
  .split(",")
  .map(_ split "=")
  .map {
    case v @ Array(k) => println(s"bad key: $k"); v
    case v => v
  }.collect {
    case Array(k, v) => (k, v)
  }
  .toMap

A better solution (which separates side-effects from transformations) would be to use partition which would split this into two collections ("good" and "bad"), and handle each one separately:

val (good, bad) = trackingHeader
  .replaceAll("\\s", "")
  .split(",")
  .map(_ split "=")
  .partition(_.length == 2)

val trackingHeadersMap = good.map { case Array(k, v) => (k, v) }.toMap

bad.map(_(0)).foreach(k => println(s"bad key: $k"))

Upvotes: 0

Related Questions