wvlet / surface

Reflection-free object shape inspector for Scala

Version Matrix

Surface: A Reflection-Free Object Shape Inspector

Gitter Chat Build Status Coverage Status Latest version Scala.js Scaladoc

Surface is a reflection-free object surface inspection library, which is compatible between Scala and Scala.js

Overview

Surface provides functionalities to read the surface of an object, which includes:

  • Object parameter names and its types.
  • Object methods defined in an object with method argument names and types.

Surface is useful for:

  • Writing object serializer / deserializer without using any boilerplates.
  • Automatically generating code based on class parameters, method definitions, etc.
  • Dependency injection based on object shapes (e.g., Airframe)

Usage

Maven Central

build.sbt

libraryDependencies += "org.wvlet" %% "surface" % "(version)"

// For Scala.js
libraryDependencies += "org.wvlet" %%% "surface" % "(version)"

Surface.of[X]

import wvlet.surface._

case class A(id:Int, name:String)

val surface = Surface.of[A]
println(surface.toString) // This will show A(id:Int, name:String)

// Find object parameters
surface.params.mkString(", ") // Returns "id:Int, name:String"

// Object factory
surface.objectFactory.map{ f =>
  f.newInstance(Seq(1, "leo"))
}
// Some(A(1, "leo"))

Type alias

type UserName = String

Surface.of[UserName] //  Returns UserName:=String

Tagged Type

To have different surfaces for the same type, you can use tagged type (@@):

import wvlet.surface.Surface
import wvlet.surface.tag._

class Fruit
trait Apple
trait Banana

Surface.of[Fruit @@ Apple]
Surface.of[Fruit @@ Banana]

Runtime Annotation

Reading runtime-annotation is supported for JVM projects. Import wvlet.surface.runtime._ to use this feature.

import wvlet.surface.runtime._
import javax.annotation.Resource
 
@Resource(name="my resource")
class A(@Resource(name = "param 1") a:String)  

val s = Surface.of[A]
// Reading class annotation
val a = s.findAnnotationOf[Resource]
a.get.name // "my resource" 

// Reading parameter annotation
val r = s.params.find(_.name == "a").findAnnotationOf[Resource]
r.get.name // "param 1"

Discussion

Why not using Scala reflection?

Because runtime reflection is slow and postpones detecting problems related to object inspection at run-time. Surface reports any failure in object inspection at compile time. To reduce the performance overhead, Surface uses Scala Macros (planning to migrate scala.meta in future) to perform object inspection at compile time.