DEV Community

feoktant
feoktant

Posted on

Status field in JSON responses

Sometimes I see pattern in REST JSON API responses with status field:

field type description
success boolean True or false. If false then only the two last parameters will be appended. Default value: true
userId string Created user id. For example: "00000000-0000-0000-0000-000000000000"
errCode number If success false: Custom error code. For example: 210003
errMsg string If success false: Custom error message. For example: "Transaction failed"

And such success field appears in all responses, forming what will be in response.
Let's implement this pattern in Scala.

Step one: modeling response

First of all, lets think about responses. They all share the same status field. Error should have false and successful responses be true:

trait Response {
  val success: Boolean
}

trait ErrorMixin extends Response {
  val success: Boolean = false 
}

trait SuccessMixin extends Response {
  val success: Boolean = true 
}

If we return error, we do not need to write other fields except errCode and errMsg.
And by other side, no need to pass nulls on success in errCode and errMsg.
Such two case classes will look like this, without our status code.

case class Error(
  errCode: Int,
  errMsg: String
) extends ErrorMixin

case class UserIdResponse(
  userId: UUID
) extends SuccessMixin

Status code will be mapped in type by Either.
Since our app can have lots of endpoints and we want to have consistent error responses, I'll add type to show this explicitly:

type Result[S <: SuccessMixin] = Either[Error, S]

def findUser(req: RequestDto): Result[UserIdResponse] = ???

Step two: encode it to JSON

I'll use Circe lib to show how to encode these DTO's, but it should not be hard to adopt it for PlayJson or other lib.

Circe will give us Either encoder implicitly, so we need to add only status field beforehand:

def withSuccess[R <: Response](enc: Encoder[R]): Encoder[R] = (r: R) =>
  enc.apply(r).mapObject(_.+:("success", r.success.asJson))

import io.circe._, io.circe.syntax._

implicit val userCreatedEncoder: Encoder[UserIdResponse] =
  withSuccess(deriveEncoder[UserIdResponse])

implicit val errorEncoder: Encoder[Error] =
  withSuccess(deriveEncoder[Error])

And that's all! Status field will be always on it's place.
Now it is easy to map status codes for left part depending on error happened.

Top comments (0)