Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added selective field getters for JsObject #63

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions src/main/scala/spray/json/JsValue.scala
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,34 @@ sealed abstract class JsValue {
case class JsObject(fields: Map[String, JsValue]) extends JsValue {
override def asJsObject(errorMsg: String) = this
def getFields(fieldNames: String*): Seq[JsValue] = fieldNames.flatMap(fields.get)

def getField(fieldName: String):Option[JsValue] = fields.get(fieldName)

def getString(fieldName: String):Either[String, String] = fields.get(fieldName) match {
case Some(JsString(str)) => Right(str)
case Some(x) => Left("Expected a JsString in field %s but got %s".format(fieldName, x))
case None => Left("Field %s is missing".format(fieldName))
}

def getNumber(fieldName: String):Either[String, BigDecimal] = fields.get(fieldName) match {
case Some(JsNumber(num)) => Right(num)
case Some(x) => Left("Expected a JsNumber in field %s but got %s".format(fieldName, x))
case None => Left("Field %s is missing".format(fieldName))
}

def getInt(fieldName: String):Either[String, Int] = getNumber(fieldName).right.map(_.toInt)

def getLong(fieldName: String):Either[String, Long] = getNumber(fieldName).right.map(_.toLong)

def getFloat(fieldName: String):Either[String, Double] = getNumber(fieldName).right.map(_.toFloat)

def getDouble(fieldName: String):Either[String, Double] = getNumber(fieldName).right.map(_.toDouble)

def getBoolean(fieldName: String):Either[String, Boolean] = fields.get(fieldName) match {
case Some(JsBoolean(bool)) => Right(bool)
case Some(x) => Left("Expected a JsBoolean in field %s but got %s".format(fieldName, x))
case None => Left("Field %s is missing".format(fieldName))
}
}
object JsObject {
// we use a ListMap in order to preserve the field order
Expand Down
44 changes: 43 additions & 1 deletion src/test/scala/spray/json/CustomFormatSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class CustomFormatSpec extends Specification with DefaultJsonProtocol {

case class MyType(name: String, value: Int)

implicit val MyTypeProtocol = new RootJsonFormat[MyType] {
val MyTypeProtocol = new RootJsonFormat[MyType] {
def read(json: JsValue) = {
json.asJsObject.getFields("name", "value") match {
case Seq(JsString(name), JsNumber(value)) => MyType(name, value.toInt)
Expand All @@ -32,14 +32,56 @@ class CustomFormatSpec extends Specification with DefaultJsonProtocol {
def write(obj: MyType) = JsObject("name" -> JsString(obj.name), "value" -> JsNumber(obj.value))
}

val MyTypeProtocolWithOptionals = new RootJsonFormat[MyType] {

def maybeJsObject(json:JsValue):Either[String,JsObject] =
try{ Right(json.asJsObject) }
catch{ case e:Throwable => Left(e.getMessage)}

//this could be more elegant with a for comprehension but currently definitions are not allowed with either
//https://issues.scala-lang.org/browse/SI-5793
def maybeMyType(json:JsValue):Either[String,MyType] = maybeJsObject(json).right.flatMap{ obj =>
obj.getString("name").right.map{ name =>{
val value = obj.getInt("value").right.getOrElse(42)
MyType(name,value)
}}
}

def read(json: JsValue) = maybeMyType(json) match {
case Right(my) => my
case Left(error) => deserializationError(error)
}

def write(obj: MyType) = JsObject("name" -> JsString(obj.name), "value" -> JsNumber(obj.value))
}

"A custom JsonFormat built with 'asJsonObject'" should {
val value = MyType("bob", 42)

"correctly deserialize valid JSON content" in {
implicit val format = MyTypeProtocol
"""{ "name": "bob", "value": 42 }""".asJson.convertTo[MyType] mustEqual value
}

"support full round-trip (de)serialization" in {
implicit val format = MyTypeProtocol
value.toJson.convertTo[MyType] mustEqual value
}

"support optional parameters" in {
implicit val format = MyTypeProtocolWithOptionals
"""{ "name": "bob" }""".asJson.convertTo[MyType] mustEqual value
}

"allow overriding default parameters" in{
implicit val format = MyTypeProtocolWithOptionals
"""{ "name": "mike", "value": 123 }""".asJson.convertTo[MyType] mustEqual MyType("mike", 123)
}

"throw an exception if a required field is mission" in{
implicit val format = MyTypeProtocolWithOptionals
"""{ "value": 123 }""".asJson.convertTo[MyType] must throwA(new DeserializationException("Field name is missing"))
}
}

}