playframework / play-json

The Play JSON library
Apache License 2.0
361 stars 133 forks source link

Feature request: Support Either in Json.format #50

Closed graingert closed 9 months ago

graingert commented 7 years ago

The implementation is fairly simple: https://gist.github.com/graingert/7b1c9d20fb5f4cb081dd5a640ca335f4#file-jseither-scala

but I don't see why it's not built in.

dhpiggott commented 7 years ago

I don't think that's necessarily the implementation that users would expect. It assumes that the types don't overlap, i.e. that there is no value of B that when written could then be interpreted as a valid A on reading.

For example, if A was case class Employee(name: String), and B was case class IceCream(name: String, numCherries: Int), then {"name": "Sundae", "numCherries":1} would read as Left(Employee(Sundae)), when it should actually read as Right(IceCream(Sundae, 1)).

I imagine it is for this reason that play-json does not provide an implicit format for eithers; it really is a decision that users need to make. In some cases users will know that their types cannot have overlapping representations, in which case your implementation may be the preferred representation. But in other cases, they will need a format that explicitly encodes whether the data should be read as an A or a B. One way to do that is as follows:

  def eitherObjectFormat[A: Format, B: Format](leftKey: String, rightKey: String): Format[Either[A, B]] =
    OFormat(
      (__ \ rightKey).read[B].map(b => Right(b): Either[A, B]) orElse
        (__ \ leftKey).read[A].map(a => Left(a): Either[A, B]),
      OWrites[Either[A, B]] {
        case Right(rightValue) => Json.obj(rightKey -> Json.toJson(rightValue))
        case Left(leftValue)   => Json.obj(leftKey  -> Json.toJson(leftValue))
      }
)
graingert commented 7 years ago

what about an Either reads that by default fails for ambiguous Eithers, but could be overridden by one that defaults to Right(v)

gmethvin commented 7 years ago

@graingert We can add the reads, but I don't think it should be provided implicitly by default.

bigjason commented 7 years ago

Why not have it include the left/right in the structure?

[{
  "employeeOrIceCream1": {
      "Left": "Cherry"
    }
},{
  "employeeOrIceCream2": {
      "Right": "Sundae"
    }
}]

There are better ways to encode it in json I am sure. Admittedly it is less useful when consuming external data, but it makes things much clearer.

asavelyev01 commented 5 years ago

One can trivially implement Writes but not Reads for general Eithers, so maybe this part is worth doing.