hypertino / binders   1.3.0

BSD 3-clause "New" or "Revised" License GitHub

binders is a Scala/Scala.js library for creating serialization/mapping libraries and frameworks.

Scala versions: 2.13 2.12 2.11
Scala.js versions: 0.6

Build Status Maven Central

Latest releases and snapshots

About

binders is a Scala/Scala.js library for creating serialization/mapping libraries and frameworks.

The aim of binders is to allow easily make fast (compile-time/macro based) serialization library for case-classes, collections, primitives. It takes out most of the complexity dealing with macro.

Another thing provided by binders is a schema-less (untyped) data represented by Value type.

Existing examples of serialization libraries based on binders are:

Usage

Serialization

Implement trait Serializer[C] and provide methods with names starting write and accepts single argument with data type that is serialized. Also, to serialize classes serializer should provide a method getFieldSerializer

import com.hypertino.binders.core.Serializer
import com.hypertino.inflector.naming._

class MySerializer[C <: Converter] extends Serializer[C] {
  def writeInt(i: Int) = println(i)
  def writeString(s: String) = println(s)
  def writeNull = println("null")

  def getFieldSerializer(fieldName: String): Option[MySerializer[C]] = {
    println("serializing:" + fieldName)
    Some(new MySerializer[C])
  }
}

That's all. With this code you may "serialize" case-classes, collections, primitive types (int and string in this case).

val serializer = new MySerializer[PlainConverter.type]
serializer.bind(A(10, "hello", None))  // case class
serializer.bind(10) //primitive 
serializer.bind(Seq(1,2,3)) // collection

You may also want to implement methods beginObject/endObject and beginArray/endArray. binders will call these methods according to the type provided to call bind

PlainConverter.type is a converter of field-names that do nothing. There are more useful converters like SnakeCaseToCamelCaseConverter, DashCaseToCamelCaseConverter. They are used to transform field names according to the naming style we need.

More detailed example of serializer in json-binders is JSON serialiser

Deserialization

Implement trait Deserializer[C] and provide methods for reading primitives and other data-types. Example:

import com.hypertino.binders.core.Deserializer
import com.hypertino.inflector.naming._
class MyDeserializer[C <: Converter](val fieldName: Option[String] = None) extends Deserializer[C] {
  def readInt(): Int = 10
  def readString(): String = "hello"
  def isNull: Boolean = false

  def iterator(): Iterator[MyDeserializer[C]] = Seq("x","y","z").map(s => new MyDeserializer[C](Some(s))).toIterator
}

"deserializer" usage example:

val deserializer = new MyDeserializer[PlainConverter.type]
val a = deserializer.unbind[A] // = A(10,"Hello",Some(10))
val i = deserializer.unbind[Int] // = 10
val s = deserializer.unbind[String] // = "hello"

Full example of JSON deserializer

Value type

You may use Value type for runtime manipulation or for the schema-less data, when it's know known at a compile time exact type structure. Value type can be mixed into the typed structure if data is partially untyped. One of the examples of this is extra properties when some class have attached additional, runtime-defined properties.

Value type is a sealed trait that can only be one of the following types:

  • Obj - an object/map structure
  • Lst - list/array/sequence
  • Text - string type
  • Number - numbers internally represented as BigDecimal
  • Bool - logical type
  • Null - null object/value

Some examples:

// construct some Obj
val obj = Obj.from("a" -> Obj.from("x"  1, "y"  "yey"))

// if we have case-class we can convert it to Obj:
case class A(x: Int, y: String)  
...
val obj = A(10, "hello").toValue // equals to Obj.from("x" -> 10, "y" -> "hello)

// we also can convert it back to case-class
val a = obj.to[A]

// case class with static fields (x) and extra/dynamic fields at the same level
case class B(x: Int, extra: Obj) extends WithExtra 
val b = obj.to[B]
val obj2 = b.toValue // equals to Obj.from("x" -> 10, "y" -> "hello)

Value.dynamic method is implemented using scala Dynamic type, so you can access fields by name even they are not defined at compile-time

val obj = Obj.from("a" -> Obj.from("x"  1, "y"  "yey"))
val a = obj.dynamic.a // = Text("a")

You may also find an example of mixing Value in a case-class with json-binders

Download

libraryDependencies += "com.hypertino" %% "binders" % "1.3.0"

Releases published to Maven Central for Scala 2.11 - 2.13 JVM & JS (user %%% for Scala.js enabled projects)

Snapshots live in Sonatype repository, include it additionally:

resolvers ++= Seq(
  Resolver.sonatypeRepo("public")
)

License

binders library is available under the BSD 3-Clause License