Eric Hydrick
Eric Hydrick

Reputation: 3527

How do I get an object's type and pass it along to asInstanceOf in Scala?

I have a Scala class that reads formatting information from a JOSN template file, and data from a different file. The goal is to format as a JSON object specified by the template file. I'm getting the layout working, but now I want to set the type of my output to the type in my template (i.e. if I have a field value as a String in the template, it should be a string in the output, even if it's an integer in the raw data).

Basically, I'm looking for a quick and easy way of doing something like:

output = dataValue.asInstanceOf[templateValue.getClass]

That line gives me an error that type getClass is not a member of Any. But I haven't been able to find any other member or method that gives me an variable type at runtime. Is this possible, and if so, how?

Clarification

I should add, by this point in my code, I know I'm dealing with just a key/value pair. What I'd like is the value's type.

Specifically, given the JSON template below, I want the name to be cast to a String, age to be cast to an integer, and salary to be cast a decimal on output regardless of how it appears in the raw data file (it could be all strings, age and salary could both be ints, etc.). What I was hoping for is a simple cast that didn't require me to do pattern matching to handle each data type specifically.

Example template:

people: [{  
    name: "value",  
    age: 0,  
    salary: 0.00  
}]

Upvotes: 3

Views: 2422

Answers (3)

Nikita Volkov
Nikita Volkov

Reputation: 43310

In a type-level context the value-level terms still have a few accessors. The first one and the one you asked for is the type of the value itself (type):

output = dataValue.asInstanceOf[templateValue.type]

if the type of the value has inner members, those become available as well:

class A {
  class B {}
}

val a: A = new A
val b: a.B = new a.B

Notice b: a.B.

I must also mention how to access such members without a value-level term:

val b: A#B = new a.B

Upvotes: 0

Glen Best
Glen Best

Reputation: 23105

  1. How do I get an object's type and pass it along to asInstanceOf in Scala?

    The method scala.reflect.api.JavaUniverse.typeOf[T] requires it's type argument to be hard-coded by the caller or type-inferred. To type-infer, create a utility method like the following (works for all types, even generics - it counteracts java runtime type arg erasure by augmenting T during compilation with type tag metadata):

    // http://www.scala-lang.org/api/current/index.html#scala.reflect.runtime.package
    import scala.reflect.runtime.universe._   
    def getType[T: TypeTag](a: T): Type = typeOf[T]
    

    3 requirements here:

    • type arg implements TypeTag (but previous implementation via Manifest still available...)
    • one or more input args are typed T
    • return type is Type (if you want the result to be used externally to the method)

    You can invoke without specifying T (it's type-inferred):

    import scala.reflect.runtime.universe._
    def getType[T: TypeTag](a: T): Type = typeOf[T]
    val ls = List[Int](1,2,3)
    println(getType(ls))  // prints List[Int]
    

    However, asInstanceOf will only cast the type to a (binary consistent) type in the hierarchy with no conversion of data or format. i.e. the data must already be in the correct binary format - so that won't solve your problem.

  2. Data Conversion

    A few methods convert between Integers and Strings:

    // defined in scala.Any:
    123.toString                  // gives "123"
    // implicitly defined for java.lang.String via scala.collection.immutable.StringOps:
    123.toHexString               // gives "7b"
    123.toOctalString             // gives "173"
    "%d".format(123)              // also gives "123"
    "%5d".format(123)             // gives "  123"
    "%05d".format(123)            // gives "00123"
    "%01.2f".format(123.456789)   // gives "123.46"
    "%01.2f".format(123.456789)   // gives "0.46"
    
    // implicitly defined for java.lang.String via scala.collection.immutable.StringOps:
    "  123".toInt                 // gives 123
    "00123".toInt                 // gives 123
    "00123.4600".toDouble         // gives 123.46
    ".46".toDouble                // gives 0.46
    
  3. Parsing directly from file to target type (no cast or convert):

    Unfortunately, scala doesn't have a method to read the next token in a stream as an integer/float/short/boolean/etc. But you can do this by obtaining a java FileInputStream, wrapping it in a DataInputStream and then calling readInt, readFloat, readShort, readBoolean, etc.

Upvotes: 3

Display Name
Display Name

Reputation: 8128

Type parameters must be known at compile time (type symbols), and templateValue.getClass is just a plain value (of type Class), so it cannot be used as type parameter. What to do instead - this depends on your goal, which isn't yet clear to me... but it may look like

output = someMethod(dataValue, templateValue.getClass),

and inside that method you may do different computations depending on second argument of type Class.

Upvotes: 3

Related Questions