1
votes

I am trying to read some JSON String from request and convert them in case class in Play/Scala based REST application. My code is something like ...

 implicit val memberRead: Reads[MemberInfo] =  (
(JsPath \ "memberId").readNullable[BigInt] and
  (JsPath \ "firstName").read[String] and
  (JsPath \ "lastName").read[String] and
  (JsPath \ "janrainUUID").readNullable[String] and
  (JsPath \ "phones").read[Seq[MemberPhone]] and
  (JsPath \ "address").read[Seq[MemberAddress]]
)(MemberInfo.apply _)

implicit val addressRead: Reads[MemberAddress] =  (
(JsPath \ "addressId").readNullable[BigInt] and
  (JsPath \ "addressType").read[String] and
  (JsPath \ "address").read[String] and
  (JsPath \ "memberId").read[BigInt]
)(MemberAddress.apply _)

implicit val phoneRead: Reads[MemberPhone] =  (
(JsPath \ "phoneId").readNullable[BigInt] and
  (JsPath \ "phoneNumber").read[String] and
  (JsPath \ "phoneType").read[String] and
  (JsPath \ "primaryInd").read[String] and
  (JsPath \ "memberId").read[BigInt]
)(MemberPhone.apply _)

But I am getting some compilation error(For all three readNullable[BigInt], memberid in memberRead, addressId in addressRead and phoneId in phoneRead ). Error is ...

 No Json deserializer found for type BigInt. Try to implement an implicit Reads or Format for this type.

My Case class are some like this ...

 case class MemberInfo(memberId : Option[BigInt],firstName : String, lastName : String,janrainUUID :Option[String] , phones : Seq[MemberPhone],address : Seq[MemberAddress])
 case class MemberAddress(addressId:Option[BigInt],addressType:String,address:String,memberId:BigInt)
 case class MemberPhone(phoneId : Option[BigInt], phoneNumber:String,phoneType:String,primaryInd:String,memberId:BigInt)

for janrainUUID :Option[String] I am not getting any compilation error , but for BigInt I am getting "No Json deserializer found for type BigInt"

Any one can explain why I am getting this error for BigInt and How can I resolve those? Actually those are PK value when I will do the DB operation for those, so they never may come with request. Is there any way to express that in play/scala like @ignore annotation in jersey.

Any help will be appreciated , thanks a lot...

3
Can I know the play version which you are using?Learner

3 Answers

3
votes

You need to define serializers for BigInt in the following way:

  implicit val BigIntWrite: Writes[BigInt] = new Writes[BigInt] {
    override def writes(bigInt: BigInt): JsValue = JsString(bigInt.toString())
  }

  implicit val BigIntRead: Reads[BigInt] = Reads {
    case JsString(value) => JsSuccess(scala.math.BigInt(value))
    case JsNumber(value) => JsSuccess(value.toBigInt())
    case unknown => JsError(s"Invalid BigInt")
  }

Just add this before memberRead serializer and you are good to go and also add error handling for invalid BigInt.

2
votes

play-json doesn't provide a Reads[BigInt]. It only provides a Reads[BigDecimal].

You can either write your own Reads[BigInt]:

implicit val bigIntReads: Reads[BigInt] = implicitly[Reads[BigDecimal]].map(_.toBigInt())

or use play's Reads[BigDecimal] and transform the result:

implicit val memberRead: Reads[MemberInfo] =  (
(JsPath \ "memberId").readNullable[BigDecimal].map(_.toBigInt()) and
  ...

Edit: Both above solutions have the advantage of not re-inventing the wheel, they build on some well-tested infrastructure provided by play-json. As such they provide benefits that other solutions proposed for this question do not, mainly the correct handling of json string as well as numbers.

1
votes

You can implement Format like this and use it in your companion object as implicit val:

import play.api.libs.json._
import play.api.libs.functional.syntax._
import scala.util.Try

object BigIntFormat extends Format[BigInt] {
  override def reads(json: JsValue): JsResult[BigInt] = json match {
    case JsNumber(n) => Try(JsSuccess(n.toBigInt)).getOrElse {
      JsError(JsPath() -> JsonValidationError(s"error.expected.numeric(as BigInt), but got '$json'"))
    }
    case JsString(s) => Try(JsSuccess(BigInt(s))).getOrElse {
      JsError(JsPath() -> JsonValidationError(s"error.expected.string(as BigInt), but got '$json'"))
    }
    case _ => JsError(JsPath() -> JsonValidationError("error.expected.string"))
  }

  override def writes(o: BigInt): JsValue = JsString(o.toString)
}

case class SomethingWithBigInt(id: BigInt, str: String)

object SomethingWithBigInt {
  implicit val bigIntFormatter = BigIntFormat

  implicit lazy val format: Format[SomethingWithBigInt] = ({
    (JsPath \ "id").format[BigInt] and
      (JsPath \ "str").format[String]
  })(SomethingWithBigInt.apply, unlift(SomethingWithBigInt.unapply))
}