Xenomorph is a Scala library for building well-typed descriptions of other
Scala data structures, from which one can then automatically derive
serialization, parsing, and generation functions. Implementations are
currently provided for producing argonautDecodeJson
and EncodeJson instances, as well as scalacheckGen values. Similar facilities for scodec, protobuf, and whatever else
might be useful will be coming as time permits and interest demands.
Xenomorph is still awaiting an initial release, so for now you'll have to build it locally for yourself.
Begin with the data type for which you wish to create a schema. Here's an example:
importmonocle.macros._@LensescaseclassPerson(
name: String, birthDate: Instant,
roles: Vector[Role]
)
sealedtraitRolecaseobjectUserextendsRole {
valprism=GenPrism[Role, User.type]
}
@LensescaseclassAdministrator(department: String, subordinateCount: Int) extendsRoleobjectAdministrator {
valprism=GenPrism[Role, Administrator]
}In this example, you can see:
- A simple record type
Person - A sum type
Rolewith two constructors,UserandAdministrator
To build a schema for the Person type, we will use the Scalaz applicative
constructor at arity 3 (^^) as shown below:
importscalaz.syntax.apply._importxenomorph.Schema._importxenomorph.json.JType._valpersonSchema:Schema[JSchema, Person] = rec(
^^(
required("name", jStr, Person.name.asGetter),
required(
"birthDate", jLong.composeIso(Iso(newInstant(_:Long))((_:Instant).getMillis)), Person.birthDate.asGetter
),
required("roles", jArray(roleSchema), Person.roles.asGetter)
)(Person.apply _)
)The schema for the Role sum type is created as a nonempty list of
alternatives, each of which provides a prism from the sum type to the
underlying data type associated with each constructor. In the case of the
User case object, the underlying schema is that of the empty object, which is
isomorphic to Unit. () is the empty tuple, so we treat the empty record as
the Unit schema constructor.
valroleSchema:Schema[JSchema, Role] =Schema.oneOf(
alt[JSchema, Role, User.type](
"user", Schema.const(User),
User.prism
) ::
alt[JSchema, Role, Administrator](
"administrator", rec(
^(
required("department", jStr, Administrator.department.asGetter),
required("subordinateCount", jInt, Administrator.subordinateCount.asGetter)
)(Administrator.apply _)
),
Administrator.prism
) :: shapeless.HNil
)This schema is constructed using the JType GADT to define the set of recognized
primitive types. However, the set of primitive types is a user-definable feature at
the time of schema construction.
Once you have a Schema value, you can use it to produce parsers, serializers, and generators.
importargonaut._importxenomorph.json.ToJson._importxenomorph.json.FromJson._importxenomorph.scalacheck.ToGen._valpersonJson:Json= personSchema.toJson(person) valparsedPerson:Option[Person] = personSchema.fromJson(personJson).toOption
valpersonGen:Gen[Person] = personSchema.toGenKris Nuttycombe (@nuttycom) Antonio Alonso Dominguez Doug Clinton