vincentlcy
vincentlcy

Reputation: 1217

Elegant way handling both missing key and null values from Scala Map

I understand

e.g.

map.getOrElse("key1","default")

Meanwhile I am interacting with a Java library, which some values are null.

e.g. Map("key1"->null)

getOrElse will throw null pointer in this case.

I want to handle both cases and result in writing something like this

  def getOrElseNoNull[A,B](map:Map[A,B],key:A,default:B) = {
    map.get(key) match{
      case Some(x) if x != null => x
      case _ => default
    }
  }

which is quite ugly. (it is Map[Any] and I need a string from that key)

getOrElseNoNull(map,"key1","").asInstanceOf[String])

is it possible to use implicit to extend the map, or any other elegant way?

Upvotes: 5

Views: 15221

Answers (5)

Himanshu Ahire
Himanshu Ahire

Reputation: 717

Another simple solution is to wrap map.get result with Scala Option.

 val value = Option(map.get(key))

Upvotes: 0

Régis Jean-Gilles
Régis Jean-Gilles

Reputation: 32719

If you're dealing with an immutable Map, the safest thing to do would be to filter out all the null values up front (this incurs the creation of yet another Map instance, but unless you have a specific reason to care about performance here it should not be an issue).

val withoutNulls = map.filter{case (k,v)=> v != null}

Any key that was holding a null is gone, and as such getOrElse on this key will return None. Trivial, and does the job once for all.

Upvotes: 11

Lukas Wegmann
Lukas Wegmann

Reputation: 478

One possibility is to map the values over Option():

val withoutNulls: Map[Int, Option[String]] = withNulls.mapValues(Option.apply)

This gives you the possibility to handle missing values and nulls the same way:

val nullsEqualMissing: Map[Int, Option[String]] = withoutNulls.withDefaultValue(None)
nullsEqualMissing(1).fold{ "nullOrMissing" }{ identity }

Or to handle missing values separately:

withoutNulls.get(1).fold{ "missing" }{ _.fold{ "null" }{ identity }}

Upvotes: 1

Noam
Noam

Reputation: 1022

There is no real need to create something new, scala supports several methods to achieve this with default values:

// define a function to handle special values
Map("1" -> "2").withDefault( _ => "3").apply("4")

// default values for all unknown values
Map("1" -> "2").withDefaultValue("3").apply("4")

// handle a specific case
Map("1" -> "2").getOrElse("unknown", "3")

Another option is to use the Option to get the null value in a less ugly way:

None.orNull

This will get you the Option value or return null in case of None.

Upvotes: 0

Sean Vieira
Sean Vieira

Reputation: 159855

Implicit extension classes to the rescue:

implicit class NullOccludingMap[K, V](private val underlying: Map[K, V]) extends AnyVal {
  def getNonNullOrElse(key: K, default: V): V = {
    underlying.get(key) match {
      case Some(value) if value != null => value
      case _ => default
    }
  }
}

Then you can use it anywhere it is in scope:

val test = Map("x" -> "Hi", "y" -> null)
test.getNonNullOrElse("z", "") // ""
test.getNonNullOrElse("y", "") // ""

Upvotes: 5

Related Questions