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": []
}

      

+3


source to share


2 answers


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.

+1


source


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 yours Format

    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")
}

      

+1


source







All Articles