How to send Json from client with missing fields for corresponding Case class after using Json.format function
I have a Case class and its companion object as shown below. Now when I send JSON without id, createdAt and deletedAt, because I have set them elsewhere, I get an error [NoSuchElementException: JsError.get]
. This is because I am not setting the above properties.
How can I achieve this and avoid the error?
case class Plan(id: String,
companyId: String,
name: String,
status: Boolean = true,
@EnumAs planType: PlanType.Value,
brochureId: Option[UUID],
lifePolicy: Seq[LifePolicy] = Nil,
createdAt: DateTime,
updatedAt: DateTime,
deletedAt: Option[DateTime]
)
object Plan {
implicit val planFormat = Json.format[Plan]
def fromJson(str: JsValue): Plan = Json.fromJson[Plan](str).get
def toJson(plan: Plan): JsValue = Json.toJson(plan)
def toJsonSeq(plan: Seq[Plan]): JsValue = Json.toJson(plan)
}
JSON I am sending from client
{
"companyId": "e8c67345-7f59-466d-a958-7c722ad0dcb7",
"name": "Creating First Plan with enum Content",
"status": true,
"planType": "Health",
"lifePolicy": []
}
source to share
You can introduce another case class just to handle serialization from a request: like this
case class NewPlan(name: String,
status: Boolean = true,
@EnumAs planType: PlanType.Value,
brochureId: Option[UUID],
lifePolicy: Seq[LifePolicy] = Nil
)
and then use this class to populate your Plan class.
source to share
The main problem is that by the time a is created case class
to represent your data, it must be well typed. To train the beep with your sample data into your sample class, the types don't match because some fields are missing. It literally tries to call the constructor with no sufficient arguments.
You have a couple of options:
- You can create a model that represents incomplete data (as Guntianster suggested).
- You can specify possible field types
Option
. - You can write
Reads
part of yoursFormat
to enter smart values ββor dummy values ββfor missing ones.
Option 3 might look something like this:
// Untested for compilation, might need some corrections
val now: DateTime = ...
val autoId = Reads[JsObject] {
case obj: JsObject => JsSuccess(obj \ 'id match {
case JsString(_) => obj
case _ => obj.transform(
__.update((__ \ 'id).json.put("")) andThen
__.update((__ \ 'createdTime).json.put(now)) andThen
__.update((__ \ 'updatedTime).json.put(now))
)
})
case _ => JsError("JsObject expected")
}
implicit val planFormat = Format[Plan](
autoId andThen Json.reads[Plan],
Json.writes[Plan])
Once you do this once, if the problem is the same for all of your other models, you can perhaps abstract it in some kind of Format
factory utility .
This might be a little cleaner for autoId
:
val autoId = Reads[JsObject] {
// Leave it alone if we have an ID already
case obj: JsObject if (obj \ 'id).asOpt[String].isSome => JsSuccess(obj)
// Insert dummy values if we don't have an `id`
case obj: JsObject => JsSuccess(obj.transform(
__.update((__ \ 'id).json.put("")) andThen
__.update((__ \ 'createdTime).json.put(now)) andThen
__.update((__ \ 'updatedTime).json.put(now))
))
case _ => JsError("JsObject expected")
}
source to share