You're right that merge patch could be implemented still one level up. I suppose I should have said it would be awkward to implement in a re-usable way (since you'd have to re-write the reads for the parent object by hand rather than implementing a reads for some three case algebraic data type).
Couldn't you still implement backwards compatible migrations if the proposed feature was added? True you couldn't deprecate an absent-sensitive field, but I'm not sure you could in any case.
Something like (forgive the dark formatting)
case class MyModel(timestamp: Instant)
val modelReads = Reads { json ⇒
val maybeDeprecatedWay = for {
zone ← (json \ "zone").as[Option[ZoneId]]
dateTime ← (json \ "dateTime").as[Option[LocalDateTime]]
} yield dateTime.atZone(zone).toInstant
maybeDeprecatedWay
.map(JsSuccess(_))
.getOrElse((json \ "timestamp").validate[Instant])
.map(MyModel)
}
is quite similar to how it would be done today for the example you gave. We haven't taken away the ability to handle things on the outer object reads, just added the ability to do more in the inner reads (which means we no longer need `
asOpt
`).