1

I have a map of type Map[_, Any], and I want to extract the values in their native format (without resorting to .asInstanceOf[_]).

Something like this...

val m: Map[String, Any] = Map("i" -> 1, "s" -> "test")

val i: Option[Int] = m.get("i")
val s: Option[String] = m.get("s")

Obviously that fails.

I don't like this approach, but I was thinking I could do something like this... but even this still comes out as Any instead of Int or String.

trait MyType[A] {
  def value: A
}

implicit class MyInt(i: Int) extends MyType[Int] { def value: Int = i }
implicit class MyString(s: String) extends MyType[String] { def value: String = s }

val m: Map[String, MyType[_]] = Map("i" -> 1, "s" -> "test")

val i: Option[Int] = m.get("i").map(_.value)
val s: Option[String] = m.get("s").map(_.value)

Then I thought maybe some wrapper around the Map...

case class MyMap(m: Map[String, Any]) {
  def get[A](k: String)(implicit ev: Option[Any] => Option[A]): Option[A] = m.get(k)
}

But that STILL comes out as Any. I just can't figure out how to convert Any => native.

So my questions are...

  1. Why does this fail?
  2. What are better way(s) to get the values out in their native format? Simplest and/or no external dependencies would be ideal... but I'm honestly open to anything (though caveat I'm still on scala 2.11 for now).

Thanks!

Andrey Tyukin
  • 43,673
  • 4
  • 57
  • 93
kmh
  • 1,516
  • 17
  • 33
  • 1
    Why wouldn't it fail? Compiler has no way of knowing that a value associated with some random string would turn out to be `Int` or `String`. – Oleg Pyzhcov May 10 '21 at 20:13
  • 3
    The moment you have an **Any** that is all you will know about it. Your approach doesn't even make sense the **Map** is probably a runtime value, how would the compiler travel in time to infer which type the value will have in all the endless possibilities? - A better question would be, what is the problem you are trying to solve? This looks like a **Json**. – Luis Miguel Mejía Suárez May 10 '21 at 20:14
  • 3
    See also: https://stackoverflow.com/a/22852109/2707792 – Andrey Tyukin May 10 '21 at 21:42

1 Answers1

4

You cannot guess the runtime type for the reasons that have already been explained in the comments - this information is not there, once it's Any, all type information is lost, there is nothing you can do about it.

So, you'll have to provide the expected type yourself. How about an .as[T] helper method?

// This code is specifically for 2.11, please don't use it for more recent versions,
// see link below.

val m: Map[String, Any] = Map("i" -> 1, "s" -> "test")

import scala.reflect.{ ClassTag, classTag }

implicit class As(a: Any) {
  def as[T](implicit ct: ClassTag[T]): Option[T] = ct.unapply(a)
}

println(m("i").as[Int].map(_ + 41).get)
println(m("s").as[String].map("This is a " + _).get)

This will print

42
This is a test

Brief explanation:

  • The As wrapper "pimps" all the objects, and attaches an .as method to everything.
  • The unapply does the checking, the casting, and the wrapping in an Option.

It will not work for generic types like List[Int] vs. List[String] etc, because this information is simply not available at runtime.

EDIT: Thanks @MarioGalic for greatly simplifying the solution.

Andrey Tyukin
  • 43,673
  • 4
  • 57
  • 93