Created
March 28, 2016 14:02
-
-
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 file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
implicit val formats = DefaultFormats + Password.Format | |
val json = ("_id", JString("user-1234")) ~ | |
("_rev", JString("1-0abc2")) ~ | |
("email", JString("[email protected]")) ~ | |
("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