bizzabo / play-json-extensions

+22 field case class formatter and more for play-json
http://cvogt.org/play-json-extensions/api/
Other
195 stars 47 forks source link

Add Jsonx.oFormatSealed method #36

Closed clamothe closed 7 years ago

clamothe commented 7 years ago

This PR contains and depends on PR #35

We use play-json-extensions in conjunction with ReactiveMongo-Play-Json. The various Jsonx methods are used to create Format[T] for our ADT's. ReactiveMongo-Play-Json uses these formatters in it's process of serializing instances of our ADT to Mongo BSON documents.

Only JsObjects can be persisted as documents using ReactiveMongo's play module. It accepts OWrites (or OFormat) only. This is natural since Mongo is a document store rather than a key-value store.

Jsonx.formatSealed generates a Format rather than an OFormat, and for good reason: the SingletonEncoders all write case objects as JsStrings.

In our use case, we use Jsonx.formatSealed to create formatters for ADTs that have no case objects, yet we wish to persist these ADTs as documents to Mongo.

Our current type-unsafe solution is the following format wrapper:


/**
  * Wraps jsonx format which doesn't produce an OFormat to create an OFormat compatible writes.
  * @param f Underlying format object
  * @tparam T Serialization type
  */
case class ObjectFormat[T](f: Format[T]) extends OFormat[T] {
  override def writes(o: T): JsObject = f.writes(o).asInstanceOf[JsObject]
  override def reads(json: JsValue): JsResult[T] = f.reads(json)
}

Of course, if our ADT contained a singleton case object, we'd eventually discover that the above wrapper would result in an error.. at runtime.

If there were a version of Jsonx.formatSealed that limited itself to OFormat, we wouldn't need to rely on asInstanceOf or another runtime type-check.

This PR contains such a method. In order for a call to Jsonx.oFormatSealed to compile, all known direct subclasses of the sealed trait must have an implicitly available OFormat.

If we were to add a case object, formatted using formatSingleton, to a sealed trait that we write as a mongo document, this would result in a compile-time error. We could create JsObject-based Format for Singletons if we need this. Previously, it would result in a runtime-error when ObjectFormat#writes encounters a non-JsObject.