/* * Copyright 2016 Coursera Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.coursera.common.jsonformat import org.coursera.common.stringkey.StringKeyFormat import play.api.libs.json.Format import play.api.libs.json.Json import play.api.libs.json.OFormat import play.api.libs.json.OWrites import play.api.libs.json.Reads import play.api.libs.json.Writes import play.api.libs.json.__ /** * Formats a subclass of a variable-schema superclass. * * Variable-schema objects must have a type name (string) and a definition (JSON). The schema of * the definition is specified based on the type name. These formats read the type name and then * appropriately handle the JSON definition. * * For example, the type names for a user id object may be "guest" and "registered" and the * corresponding definitions may include a random string for the guest and an integer id for * the registered user. * {{{ * sealed trait UserId * * case class GuestUserId(id: String) * * object GuestUserId { * implicit val format: OFormat[GuestUserId] = * TypedFormats.typedDefinitionFormat("guest", Json.format[GuestUserId]) * } * * case class RegisteredUserId(id: Int) * * object RegisteredUserId { * implicit val format: OFormat[RegisteredUserId] = * TypedFormats.typedDefinitionFormat("registered", Json.format[RegisteredUserId]) * } * }}} * * The JSON object looks like: * {{{ * { * "typeName": "<some name>", * "definition": <some JSON> * } * }}} * See [[OrFormats]] for defining `UserId`'s [[OFormat]] in this example. */ object TypedFormats { def typedDefinitionFormat[T, D]( typeName: T, defaultFormat: Format[D]) (implicit stringKeyFormat: StringKeyFormat[T]): OFormat[D] = { OFormat( typedDefinitionReads(typeName, defaultFormat), typedDefinitionWrites(typeName, defaultFormat)) } def typedDefinitionReads[T, D]( typeName: T, defaultReads: Reads[D]) (implicit stringKeyFormat: StringKeyFormat[T]): Reads[D] = { import JsonFormats.Implicits.ReadsPathMethods implicit val typeNameFormat = JsonFormats.stringKeyFormat[T] for { _ <- (__ \ "typeName").read[T].filter(_ == typeName).withRootPath definition <- { val definitionExtractingReads = (__ \ "definition").json.pick.withRootPath defaultReads.compose(definitionExtractingReads) } } yield { definition } } def typedDefinitionWrites[T, D]( typeName: T, defaultWrites: Writes[D]) (implicit stringKeyFormat: StringKeyFormat[T]): OWrites[D] = { implicit val typeNameFormat = JsonFormats.stringKeyFormat[T] OWrites { model: D => val modelJson = Json.toJson(model)(defaultWrites) Json.obj("typeName" -> typeName, "definition" -> modelJson) } } }