Skip to content

Instantly share code, notes, and snippets.

@sam
Created March 28, 2016 14:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sam/6b409f095c9eb7caa674 to your computer and use it in GitHub Desktop.
Save sam/6b409f095c9eb7caa674 to your computer and use it in GitHub Desktop.
How to transform a JSON field into a composed class member during serialization.
// This is just a basic sealed trait implementing a couple cases.
sealed trait Password {
val value: String
def hashed: HashedPassword
def check(plainTextCandidate: String): Boolean
}
case class PlainTextPassword(value: String) extends Password {
def hashed = HashedPassword.Scrypt(value)
def check(plainTextCandidate: String) = plainTextCandidate == value
}
case class HashedPassword(value: String) extends Password {
def hashed = this
def check(plainTextCandidate: String) = Scrypt.check(value, plainTextCandidate)
}
// And here we have our serialization. It's pretty straight-forward since even though
// we need to manipulate the class and value at serialization/deserialization,
// it's a 1:1 mapping from the class to JSON, and the field name and class names are
// the same.
object Password extends (String => PlainTextPassword) {
def apply(plainText: String) = PlainTextPassword(plainText)
private[this] val read: Formats => PartialFunction[JValue, Password] = formats => {
case JString(value) => HashedPassword(value)
}
private[this] val write: Formats => PartialFunction[Any, JValue] = formats => {
case HashedPassword(value) => JString(value)
case password @ PlainTextPassword(_) => JString(password.hashed.value)
}
object Format extends CustomSerializer[Password](formats => (read(formats), write(formats)))
}
// This is our Id type. Cloudant (CouchDB) returns _id and _rev members in
// the JSON that we want to compose into a single object to avoid "stringly typed" members.
sealed trait Id {
val id: String
}
case class NewId(id: String) extends Id
case class IdWithRev(id: String, rev: String) extends Id
// Here we use our Password member. Which is fine. We've written a serializer for it,
// but we also use our Id here, which requires more work:
case class User(key: Id, email: String, password: Password)
// A default mapping would look like this:
case class User(_id: String, _rev: Option[String], password: Password)
implicit val formats = DefaultFormats + Password.Format
val json = ("_id", JString("user-1234")) ~
("_rev", JString("1-0abc2")) ~
("email", JString("me@example.com")) ~
("password", JString("$s0$e0801$CQdMqMrO2THZ4aa1eyPBew==$BEeFifVXtG/ju1y68Kxm/YkWFhyXoHrwjL15Pnbw06w="))
// NOTE: There is no "key" field in the JSON, so our formats will throw
// org.json4s.package$MappingException: No usable value for key
// Can't convert JNothing to interface Id.
json.extract[User]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment