Giter Site home page Giter Site logo

play-json-derived-codecs's Introduction

(Note: this project has been renamed from play-json-variants to play-json-derived-codecs)

Play JSON Derived Codecs

Reads, OWrites and OFormat derivation for algebraic data types (sealed traits and case classes, possibly recursive), powered by shapeless.

Compared to the built-in macros, this project brings support for:

  • sealed traits ;
  • recursive types ;
  • polymorphic types.

The artifacts are built for Scala and Scala.js 2.12, and 2.13, Play 2.9 and Shapeless 2.3.

For Play 2.8 compatibility see version 7.0.0.

Usage

import julienrf.json.derived

case class User(name: String, age: Int)

object User {
  implicit val reads: Reads[User] = derived.reads()
}

The API is simple: the object julienrf.json.derived has just three methods.

  • reads[A](), derives a Reads[A] ;
  • owrites[A](), derives a OWrites[A] ;
  • oformat[A](), derives a OFormat[A].

Representation of Sum Types

By default, sum types (types extending a sealed trait) are represented by a JSON object containing one field whose name is the name of the concrete type and whose value is the JSON object containing the value of the given type.

For instance, consider the following data type:

sealed trait Foo
case class Bar(s: String, i: Int) extends Foo
case object Baz extends Foo

The default JSON representation of Bar("quux", 42) is the following JSON object:

{
  "Bar": {
    "s": "quux",
    "i": 42
  }
}

Configuring the Derivation Process

Three aspects of the derivation process can be configured:

  • the representation of sum types,
  • the way case class field names are mapped to JSON property names,
  • the type name used to discriminate sum types.

Custom Representation of Sum Types

The default representation of sum types may not fit all use cases. For instance, it is not very practical for enumerations.

For instance, you might want to represent the Bar("quux", 42) value as the following JSON object:

{
  "type": "Bar",
  "s": "quux",
  "i": 42
}

Here, the type information is flattened with the Bar members.

You can do so by using the methods in the derived.flat object:

implicit val fooOWrites: OWrites[Foo] =
  derived.flat.owrites((__ \ "type").write[String])

In case you need even more control, you can implement your own TypeTagOWrites and TypeTagReads.

Custom Field Names Mapping

By default, case class fields are mapped to JSON object properties having the same name.

You can transform this mapping by supplying a different NameAdapter parameter. For instance, to use “snake case” in JSON:

implicit val userFormat: OFormat[User] = derived.oformat(adapter = NameAdapter.snakeCase)

Custom Type Names

By default, case class names are used as discriminators (type tags) for sum types.

You can configure the type tags to use by using the derived.withTypeTag object:

implicit val fooFormat: OFormat[Foo] =
  derived.withTypeTag.oformat(TypeTagSetting.FullClassName)

The library provides the following TypeTagSetting values out of the box:

  • TypeTagSetting.ShortClassName: use the class name (as it is defined in Scala)
  • TypeTagSetting.FullClassName: use the fully qualified name
  • TypeTagSetting.UserDefinedName: require the presence of an implicit CustomTypeTag[A] for all type A of the sum type, providing the type tag to use

Custom format for certain types in hierarchy

Sometimes, you might want to represent one type differently than default format would. This can be done by creating an implicit instance of DerivedReads or DerivedWrites for said type. Below is an example of implementing both custom reads and writes for a single class in a hierarchy:

sealed trait Hierarchy
case class First(x: Integer)
case class Second(y: Integer)

implicit val SecondReads: DerivedReads[Second] = new DerivedReads[Second] {
  def reads(tagReads: TypeTagReads, adapter: NameAdapter) = tagReads.reads("Second", (__ \ "foo").read[Integer].map(foo => Second(foo)))
}

implicit val SecondWrites: DerivedOWrites[Second] = new DerivedOWrites[Second] {
  override def owrites(tagOwrites: TypeTagOWrites, adapter: NameAdapter): OWrites[Second] =
    tagOwrites.owrites[Second](
      "Second",
      OWrites[Second](s => JsObject(("foo", Json.toJson(s.y)) :: Nil))
    )
}

val defaultTypeFormat = (__ \ "type").format[String]
implicit val HierarchyFormat = derived.flat.oformat[Hierarchy](defaultTypeFormat)

This will cause Second to be read with SecondReads, and read with SecondWrites.

Avoiding redundant derivation

By default, the auto-derivation mechanism will be applied to the whole sealed hierarchy. This might be costly in terms of compile-time (as Shapeless is being used under the hood). To avoid this, it is possible to define an Format for the different cases, thus only using auto-derivation for the branching in the sealed trait and nothing else.

sealed trait Hierarchy

case class First(a: Int, b: Int, c: Int) extends Hierarchy
case class Second(x: Int, y: Int, c: Int) extends Hierarchy

object First {
  implicit val format: OFormat[First] = Json.format
}

object Second {
  implicit val format: OFormat[Second] = Json.format
}

implicit val HierarchyFormat = derived.oformat[Hierarchy]()

Important note: in case derived.flat is being used, it's recommended that the provided Formats actually produce JsObjects. If that's not the case, a synthetic wrapper around the user-provided result will be generated on-the-fly. For this reason, Json.valueFormat and the like are not compatible with derived.flat, and it is best to avoid using them together.

Here is what will happen if they are used together:

sealed trait Foo
case class Bar(x: Int) extends Foo

object Bar {
  implicit val format: Format[Bar] = Json.valueFormat
}

implicit val fooFormat = derived.flat.oformat[Foo]((__ \ "type").format[String])

Json.toJson(Bar(42)) // { "type": "Bar", "__syntheticWrap__": 42 }

Without the provided Formats the derivation mechanism will traverse all the fields in the hierarchy (in this case 6 in total), which may be costly for larger case classes.

Providing the implicits this way can also be used for customization without having to deal with supplying your own type-tags.

Contributors

See here.

Changelog

See here.

play-json-derived-codecs's People

Contributors

dimitriho avatar geluoltean avatar jeantil avatar julienrf avatar kamac avatar lkt avatar meln1k avatar mliarakos avatar ncreep avatar robstoll avatar sarahgerweck avatar tflucke avatar xnejp03 avatar xuwei-k avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

play-json-derived-codecs's Issues

Compiler failure when subclasses are not imported in the scope

Given following code:

package actors

import akka.actor.{Actor, ActorRef}
import models.{Channel, Message, User}

object ChatActor {
  type MsgId = Int

  sealed trait In
  object In {
    case class Register(user: User) extends In
    case object Unregister extends In
    case class JoinChannel(name: String) extends In
    case class LeaveChannel(name: String) extends In
    case class Msg(msgId: MsgId, channel: String, message: String) extends In
  }

And at the format generation site:

package models

import actors.ChatActor._
import julienrf.variants.Variants
import org.joda.time.DateTime
import play.api.libs.functional.syntax._
import play.api.libs.json._

object json {
  implicit val InFormat = Variants.format[In]
}

Gives of

[error] D:\work\scala\simple-chat-server\app\models\json.scala:49: not found: value Unregister
[error] Error occurred in an application involving default arguments.
[error]   implicit val InFormat = Variants.format[In]

because Unregister (which has full name of actors.ChatActor.In.Unregister) is not imported in the code scope. The error is pretty cryptic as well.

The solution would be to just use full names in generated names.

Currently you can work around it with

  implicit val InFormat = {
    import In._
    Variants.format[In]
  }

Class default values supporting

Thank you for this great library.

“play-json” default macros has Json.WithDefaultValues directive and it would be great to have the same functional here.

Package name not obvious

Usually, the import is same as the one you specify in sbt:

I think the package should be org.julienrf.variants and not julienrf.variants

Subtypes Format definitions

Sorry to bother you but I don't understand how to use your lib.

This is what I have to serialize/deserialize:

sealed trait Location
case class CityLocation(postal_code: String) extends Location
case class StoreLocation(store_code: String) extends Location
case class AddressLocation(
  society: Option[String],
  first_name: String,
  ...
) extends Location

sealed trait Point[T <: Location] { val location: T; val calendar: Option[Calendar] }
case class ShippingPoint[T <: Location](
  override val location: T,
  override val calendar: Option[Calendar] = None) extends Point[T]
case class PickupPoint[T <: Location](
  override val location: T,
  override val calendar: Option[Calendar] = None) extends Point[T]

and some example of corresponding JSONs:

{
  "type": "pickup",
  "location" : {
    "type": "city",
    "postal_code": "59000"
  }
}
{
  "type": "pickup",
  "location" : {
    "type": "address",
    "society": "Facebook",
    "first_name": "Mark",
    ...
  }
}
{
  "type": "shipping",
  "location" : {
    "type": "city",
    "postal_code": "59000"
  }
}
{
  "type": "shipping",
  "location" : {
    "type": "address",
    "society": "Facebook",
    "first_name": "Mark",
    ...
  }
}

Also, I have an API that is represented by the following case class:

case class BookApi(
  ...
  route: Tuple1[ShippingPoint[AddressLocation]],
  url: Option[String],
  ...
)

object BookApi extends TupleJson {
  implicit val format = Json.format[BookApi]
}

As you showed me, I implemented the Location and the Point formats as follow:

object Location {
  implicit val format: OFormat[Location] =
    derived.flat.oformat(
      (__ \ "type").format[String].inmap(
        tpe => s"${tpe.capitalize}Location",
        tpe => tpe.dropRight("Location".length).toLowerCase
      )
    )
}

object Point {
  implicit def format[T <: Location: Format]: OFormat[Point[T]] =
    derived.flat.oformat(
      (__ \ "type").format[String].inmap(
        tpe => s"${tpe.capitalize}Point",
        tpe => tpe.dropRight("Point".length).toLowerCase
      )
    )
}

My problem is: the BookApi implicit Format does not compile:

[error] BookApi.scala:44: No implicit format for (domain.ShippingPoint[domain.AddressLocation],) available.

Should I implement a specific Format for each of the subtypes of Point and Location ?
If yes, how could I do that ? Can I reuse the Point and Location definitions ?

make type renaming shorter

It seems like a lot of code just to e.g. rename Left and Right in Either:

  def eitherFormat[L: Format, R: Format](typeMap: Map[String, String] = Map("Left" -> "Error", "Right" -> "Ok"))(
      implicit
      derivedReads: Lazy[DerivedReads[Either[L, R]]],
      derivedOWrites: Lazy[DerivedOWrites[Either[L, R]]]): OFormat[Either[L, R]] = {
    type A = Either[L, R]
    val adapter = NameAdapter.identity

    def eitherTypeTagReads(map: Map[String, String]): TypeTagReads =
      new TypeTagReads {
        def reads[A](typeName: String, reads: Reads[A]): Reads[A] =
          (__ \ map.getOrElse(typeName, typeName)).read(reads)
      }

    def eitherTypeTagOWrites(map: Map[String, String]): TypeTagOWrites =
      new TypeTagOWrites {
        def owrites[A](typeName: String, owrites: OWrites[A]): OWrites[A] =
          OWrites[A](a => Json.obj(map.getOrElse(typeName, typeName) -> owrites.writes(a)))
      }

    OFormat[A](
      derivedReads.value.reads(eitherTypeTagReads(typeMap), adapter),
      derivedOWrites.value.owrites(eitherTypeTagOWrites(typeMap), adapter))
  }

Recursive OFormat problem (not sure if this is a limit in the language or play or your library)

neither

sealed trait SillyTrait

case class SillyObject(silly: SillyObject) extends SillyTrait

object SillyTrait {
  implicit lazy val format: OFormat[SillyTrait] = flat.oformat((__ \ "type").format[String])
}

or

sealed trait SillyTrait

case class SillyObject(silly: SillyObject) extends SillyTrait

object SillyObject {
  implicit lazy val format = Json.format[SillyObject]
}

object SillyTrait {
  implicit lazy val format: OFormat[SillyTrait] = flat.oformat((__ \ "type").format[String])
}

or

sealed trait SillyTrait

case class SillyObject(silly: SillyObject) extends SillyTrait

object SillyObject {
  implicit lazy val format = Json.format[SillyObject]
}

can be used as a representation for a json payload where the type SillyObject can be nested within SillyObject.

Is this a known limitation with Scala or Play? Can this be solved by adding extra magic with play-son-derived-codecs?

"could not find Lazy implicit value" on incremental compile

Hi!

I'm facing a strange problem, namely the following problem occures sometimes on inctremental build, but usually a full rebuild solves it:

[error] D:\projektek\Beelend\hg\beelend\app\models\events\EventLogType.scala:174: could not find Lazy implicit value of type julienrf.json.derived.DerivedReads[models.events.EventInfo]
[error]   implicit val fmtLogItemRead: Reads[EventInfo] = derived.reads[EventInfo].orElse(Reads.pure(UnknownEventInfo()))
[error]                                                                ^
[error] D:\projektek\Beelend\hg\beelend\app\models\events\EventLogType.scala:175: possible missing interpolator: detected an interpolated expression
[error]   implicit val fmtLogItemWrite: OWrites[EventInfo] = derived.owrites[EventInfo]

Anyway thanks for the great library.

Regards,
Akos

full auto derivation

As of now, if you have a nested tree of case classes for example

case class A(a: String)
case class B(a: Option[A])

You would have to write a derived instance for each class.
In kittens, @joroKr21 developed a technique (with some facilities he added to shapeless 2.3.3), we can achieve full auto derivation.
Using the same technique, a single import will provide Format instant for all case classes, while still taking a lower priority than any other existing instances. In the example above, this technique generate Format instances for B and A but respect the existing instance for Option.

It's very easy to implement here with very few code change. (mostly just need to replace your
Lazy[Writes[H]] with a Writes[H] OrElse DerivedOWrites[H] in here

If you are interested I can submit a PR. Let me know.

class XXXXX is not a case class nor a case object

I'm seeing the following error:

sealed trait A
case class B() extends A
case class C() extends A

implicit val aReads:Format[A] = Variants.format[A]

"""class A is not a case class nor a case object"""

I'm not sure what additional info would be helpful for debugging.

custom validation error message

When send json without $variant type the error message is : List((,List(ValidationError(error.expected.jsstring,WrappedArray()))))

It is not very significant. Can we try to update this message ?

Output null for None values

Hello 👋

For a case class with an optional value set to None:

case class C(a: Int, z: Option[Int])
val c = C(1, None)

...is it possible to customise derive to preserve the None values in the JSON?

E.g., to get:

{  "a": 1, "z": null }

This is possibly related to the Play note on Customize the macro to output null (hmm, if that link doesn't take you to the right section, scroll to the bottom of the page).

Surprising Implicit Resolution (Not Sure if Caused by Bug or Usage)

Given this example:

import julienrf.json.derived
import play.api.libs.json._

object Sample {

  sealed trait Animal
  object Animal {
    object Dog {
      case class Tricks(sit: Boolean, stay: Boolean)
      implicit val tricksFormat: OFormat[Tricks] = Json.format
    }
    case class Dog(name: String, age: Int, tricks: Dog.Tricks) extends Animal
    implicit val dogFormat: OFormat[Dog] = Json.format
  }

  // import Animal.Dog.tricksFormat
  implicit val animalFormat: OFormat[Animal] = derived.oformat()

}

This fails to compile with:

[error] Sample.scala:19:68: could not find Lazy implicit value of type julienrf.json.derived.DerivedReads[A]
[error] Error occurred in an application involving default arguments.
[error]   implicit val animalFormat: OFormat[Animal] = derived.oformat()

It will compile if import Animal.Dog.tricksFormat is restored. I'm mystified by this. The only OFormat instance that should be required can be resolved (while not imported into scope, it's provided by the companion object). Why isn't dogFormat enough to satisfy derived.oformat()?

Map Property in child class causes issues

Hi,
I am using the library to serialize/deserialize kafka messages into its respective classes.
I have a simple test case below:

import java.util.UUID
import julienrf.json.derived
import play.api.libs.json.{Format, Json, __}
import play.api.libs.functional.syntax._

sealed trait TestMsg

case class TestChildMsg(someString: String, someMap: Map[UUID, String]) extends TestMsg
object TestChildMsg {
  implicit val mapFormat: Format[Map[UUID, String]] = {
    implicitly[Format[Map[String, String]]]
      .inmap[Map[UUID, String]](_.map {
        case (key, value) => UUID.fromString(key) -> value
      }, _.map {
        case (key, value) => key.toString -> value
      })
  }
  implicit val format: Format[TestChildMsg] = Json.format
}

case class TestChild2Msg(someString: String, someInt: Int) extends TestMsg
object TestChild2Msg {
  implicit val format: Format[TestChildMsg] = Json.format
}

object TestMsg {
  implicit lazy val format: Format[TestMsg] =
    derived.flat.oformat((__ \ "type").format[String])
}

When I try to compile the above, I get an error:

No instance of play.api.libs.json.Format is available for scala.collection.immutable.Map[java.util.UUID, java.lang.String] in the implicit scope (Hint: if declared in the same file, make sure it's declared before)
implicit val format: Format[TestChildMsg] = Json.format

Not sure if I am doing anything wrong. I have previously used the Map[UUID, String] formatter without any issues.

Possible to improve error message in case object hierarchy isn't sealed?

Currently, the compiler error message produced in case the ADT hierarchy isn't fully sealed is not very self-explanatory. It would be great if this could be improved to help users debug issues faster.

For example (note that SuperFoo is not sealed):
`sealed trait Foo

trait SuperFoo extends Foo

case class Bar(x: Int) extends Foo

case class Baz(s: String) extends SuperFoo

case object Bah extends Foo

implicit val fooFormat: OFormat[Foo] = flat.oformat((__ \ "type").format[String])
`

Fails to compile with:
Error:(16, 58) could not find Lazy implicit value of type julienrf.json.derived.DerivedReads[A] Error occurred in an application involving default arguments. lazy implicit val fooFormat: OFormat[Foo] = flat.oformat((__ \ "type").format[String]) ^ which is not obviously caused by an unsealed ADT hierarchy

Can't figure out how to write logic to select sub-type

Hi, I have a simple problem I believe but can't figure out how to do it with this library.

`trait Actor

case class Agent(objectType: Option[String]...) extends Actor

case class Group(objectType: Option[String]...) extends Actor
`
The logic is if "objectType" is "Agent" or "Group" then reads or writes as "Agent" or "Group" respectively but if objectType is missing from json then default to an Agent.

Sorry for the basic question but I am also new to Scala :(

Derived encoding for "enums"

There should be an option to serialize sealed trait hierarchies with only case object values to just a string.

Formatters for an ADT needs to be placed after all instances

The following code fails to compile with a could not find Lazy implicit value of type julienrf.json.derived.DerivedReads[A]:

import julienrf.json.derived
import play.api.libs.json._

sealed trait Minimize

object Minimize {
  implicit val formats: OFormat[Minimize] = derived.flat.oformat((__ \ "type").format[String])
}

case class MinimizeA(a: String) extends Minimize

case class MinimizeB(b: Int) extends Minimize

The following code compiles successfully:

import julienrf.json.derived
import play.api.libs.json._

sealed trait Minimize

case class MinimizeA(a: String) extends Minimize

case class MinimizeB(b: Int) extends Minimize

object Minimize {
  implicit val formats: OFormat[Minimize] = derived.flat.oformat((__ \ "type").format[String])
}

As there is a workaround, this is not a critical bug, but it felt like a "gotcha" moment.

Do milestone release for Scala 2.12

Is there any chance that you can do a milestone release with Play 2.6 and Scala 2.12, so that people who are tracking the Play 2.6 milestones and testing Scala 2.12 will also have access to this library without having to create and maintain their own build artifacts?

Alternately, one thing I've done with my own libraries is to use different dependency versions for different Scala versions. E.g., you could do a 3.4 release that cross compiled with Play 2.5 for Scala 2.11 and Play 2.6-M3 for Scala 2.12.

Support Play 2.8

Play 2.8 is now out as of 13th December, and play-json-derived isn't compatible with it.

Can you do a Play 2.8 build, please?

Discriminator field must be configurable

When using this library as a JSON serializer for Mongo "$variant" causes problem when working with mongo manually. Therefore discriminator field must be configurable, to prevent this kind of cases.

It is unclear how to use the library

Hi!
I just curious about how make this library working. I have this code:

`
import julienrf.json.derived

sealed trait Foo
case class Baz(id: String) extends Foo
...
implicit val frontendEventFormat: Format[FrontendEvent] = derived.oformat
`

When I compile this code, I get this error:
Error:(28, 69) could not find Lazy implicit value of type julienrf.json.derived.DerivedReads[A] implicit val frontendEventFormat: Format[FrontendEvent] = derived.oformat
What's going on?

Formats for Unwrapped instances?

Does this library provide formats for datatypes with Unwrapped instances, e.g. AnyVal derived wrapper classes, such as case class Foo(value: String) extends AnyVal, which should be serialized as simple JSON string rather than as object with a value key?

Error messages are not useful

This library makes it very easy to define JSON serializers for ADTs, unfortunately the messages is produces for invalid input are not great. Regardless of the error it always produces error.sealed.trait.

I would expect the error messages to look more like this:

  • "Invalid $variantName because $fieldName could not be deserialized as $fieldTypeName"
  • "$variantName is not a valid $adtName" (when an invalid "type" field is passed)

my example won't compile

The example below won't compile. Fails with this message:

[error]   could not find Lazy implicit value of type julienrf.json.derived.DerivedReads[A]
[error]   implicit lazy val format: OFormat[Event] = flat.oformat((__ \ "type").format[String])
[error]                                                          ^
[error] one error found
[error] (compile:compileIncremental) Compilation failed
[error] application -

! @70h4nj2oc - Internal server error, for (POST) [/] ->

play.sbt.PlayExceptions$CompilationException: Compilation error[could not find Lazy implicit value of type julienrf.json.derived.DerivedReads[A]]
    at play.sbt.PlayExceptions$CompilationException$.apply(PlayExceptions.scala:27)
    at play.sbt.PlayExceptions$CompilationException$.apply(PlayExceptions.scala:27)
    at scala.Option.map(Option.scala:145)
    at play.sbt.run.PlayReload$$anonfun$taskFailureHandler$1.apply(PlayReload.scala:49)
    at play.sbt.run.PlayReload$$anonfun$taskFailureHandler$1.apply(PlayReload.scala:44)
    at scala.Option.map(Option.scala:145)
    at play.sbt.run.PlayReload$.taskFailureHandler(PlayReload.scala:44)
    at play.sbt.run.PlayReload$.compileFailure(PlayReload.scala:40)
    at play.sbt.run.PlayReload$$anonfun$compile$1.apply(PlayReload.scala:17)
    at play.sbt.run.PlayReload$$anonfun$compile$1.apply(PlayReload.scala:17)

Do I have to implement standard Json.format[Event] formatter in addition?

import play.api.mvc.{Action, BodyParsers, Controller}
import play.api.libs.json._
import julienrf.json.derived._
import scala.concurrent.Future
import Event._

class EventController extends Controller {

  def insert() = Action.async(BodyParsers.parse.json) { request =>
    request.body.validate[Event].asEither match {
      case Right(event) => println(event)
      case Left(error) => println(error.mkString)
    }
    Future.successful(Ok)
  }
}

sealed trait Event {
  val id: Option[Long]
  val links: Option[Seq[String]]
  val note: Option[String]
}

object Event {
  implicit lazy val format: OFormat[Event] = flat.oformat((__ \ "type").format[String])
}

case class Move(
  id: Option[Long],
  links: Option[Seq[String]],
  note: Option[String]
) extends Event

case class Control(
  id: Option[Long],
  links: Option[Seq[String]],
  note: Option[String]
) extends Event

Support TypeTags for

I have a larger collection of case classes all extending a sealed trait. In order to avoid naming collisions in the code these case classes are grouped into different objects (could also be packages). Now it seems the typeName used for tagging in JSON is be only the class name, so things get mixed up in this case. See example below. Is there any way to control what is used as type tag? I haven't found anything so far.

import julienrf.json.derived
import play.api.libs.json._

sealed trait Event

object Tabs { // could also be a package "tabs"
  case class OnCreated(foo: String) extends Event
  // other tab events
}

object Windows {
  case class OnCreated(foo: String) extends Event
  // other window events
}

object ConverterTest {
  def main(args: Array[String]): Unit = {
    implicit val eventFormat: Format[Event] = derived.oformat

    val tabOnCreated: Event = Tabs.OnCreated("tab created")
    val tabOnCreatedJson = Json.prettyPrint(Json.toJson(tabOnCreated))
    println(tabOnCreatedJson)
    println(Json.parse(tabOnCreatedJson).validate[Event].get.getClass)
    println

    val windowOnCreated: Event = Windows.OnCreated("window created")
    val windowOnCreatedJson = Json.prettyPrint(Json.toJson(windowOnCreated))
    println(windowOnCreatedJson)
    println(Json.parse(windowOnCreatedJson).validate[Event].get.getClass)
  }
}

Gives the following output:

{
  "OnCreated" : {
    "foo" : "tab created"
  }
}
class Tabs$OnCreated

{
  "OnCreated" : {
    "foo" : "window created"
  }
}
class Tabs$OnCreated

Execution exception MatchError

I'm trying to use variants to provide formats for a sum type in the form of:

sealed trait LinkJsonModel {
  def linkType: String
  def submittedBy: Username
  def storyId: Long
  def createdAt: Long
  def hidden: Boolean
  def id: Long
}

case class TweetLinkJsonModel(tweet: Tweet,
  submittedBy: Username,
  storyId: Long,
  createdAt: Long,
  hidden: Boolean,
  id: Long,
  linkType: String = "tweet") extends LinkJsonModel

case class PlainLinkJsonModel(linkData: PlainLinkJsonData,
  submittedBy: Username,
  storyId: Long,
  createdAt: Long,
  hidden: Boolean,
  id: Long,
  linkType: String = PlainLinkType.linkType) extends LinkJsonModel

case class VideoLinkJsonModel(linkData: VideoLinkJsonData,
  submittedBy: Username,
  storyId: Long,
  createdAt: Long,
  hidden: Boolean,
  id: Long,
  linkType: String = LinkTypes.video) extends LinkJsonModel

I also have my format defined as follows:

  implicit val format: Format[LinkJsonModel] = Variants.format[LinkJsonModel]("linkType")

When I try validating json, I get match errors at the point of Json.parse(jsonString).validate[List[LinkJsonModel]], in the form of:

play.api.Application$$anon$1: Execution exception[[MatchError: tweet (of class java.lang.String)]]

I feel like I must be doing something wrong in terms of structuring these classes. Any help would be greatly appreciated.

Add support for Play 2.6

Play 2.6 was released some days ago. It would be great to support/build an artefact for it, too

Add extra fields to DerivedOWrites

Let's say I have something like:

sealed trait Animal {
  def name: String
  def call: String
}

object Animal {
  case class Bird(name: String, timesFlownTheCoop: Int, call: String) extends Animal

  case class Cat(name: String, livesLeft: Int) extends Animal {
    override val call = "meow"
  }

  case class Dog(name: String, bonesBuried: Int) extends Animal {
    override val call = "woof"
  }

  private val jsonReads: Reads[Animal] = derived.reads()
  private val jsonOWrites: OWrites[Animal] = derived.owrites()
  implicit jsonFormat: OFormat[Animal] = OFormat(jsonReads, jsonOWrites)
}

I'm wondering if there is way that I can get the jsonOWrites to also include call. I can write my own OWrites that adds it but I was hoping that this could be derived.

From my naive view it seems like there is enough information. I get that it probably isn't in the default LabelledGeneric (if that is the correct thing to say) but perhaps I can customise somehow? I know almost nothing about Shapeless so this might not even make sense.

Getting a Variant.formatter for classes that contain an object that already has a Variant.formatter

It does not seem to be allowed for reasons I do not understand.

consider the following:

import julienrf.variants.Variants

sealed trait SA
case class Saone(a:Int) extends SA
implicit val saFormat:Format[SA] = Variants.format[SA]

sealed trait SB
case class Sbone(a:Saone) extends SB
case class Sbomo(note:String) extends SB
implicit val sbFormat:Format[SB] = Variants.format[SB]

val written = sbFormat.writes(Sbomo("hogmaster"))

There is a Saone inside one of the SBs, but this should not be a problem, as we already have an implicit formatter for SAs. However, we get a compiler error complaining No implicit Reads for controllers.Application.Saone available. on the line of the definition of sbFormat.

Oddly, removing Format[_] type annotations stops the compile error, but using the sbFormat on written's line then elicits a scala.MatchError: Sbomo(hogmaster) coming from the line on which the sbFormatter was defined(presumably it's happening somewhere in the macro expansion).

Following example doesn't work

Hello, I'm fairly sure I'm using this library correctly, but for some reason the macro is outputting an empty read and write. I first noticed this on my own use case, but it is also replicating with the basic example from the readme.

sealed trait Foo
case class Bar(x: Int) extends Foo
case class Baz(s: String) extends Foo
case class Bah(s: String) extends Foo
implicit val format = Variants.format[Foo]

Leads to

play.api.libs.json.Format[Foo](play.api.libs.json.Reads[Foo](((json) => Impl.this.defaultDiscriminator.reads(json).flatMap( match {

}))), play.api.libs.json.Writes[Foo]( match {

}))
Apply(TypeApply(Select(Select(Select(Select(Ident(TermName("play")), TermName("api")), TermName("libs")), TermName("json")), TermName("Format")), List(Ident(models.Propagate.Foo))), List(Apply(TypeApply(Select(Select(Select(Select(Ident(TermName("play")), TermName("api")), TermName("libs")), TermName("json")), TermName("Reads")), List(Ident(models.Propagate.Foo))), List(Function(List(ValDef(Modifiers(PARAM), TermName("json"), TypeTree(), EmptyTree)), Apply(Select(Apply(Select(Select(This(TypeName("Impl")), TermName("defaultDiscriminator")), TermName("reads")), List(Ident(TermName("json")))), TermName("flatMap")), List(Match(EmptyTree, List())))))), Apply(TypeApply(Select(Select(Select(Select(Ident(TermName("play")), TermName("api")), TermName("libs")), TermName("json")), TermName("Writes")), List(Ident(models.Propagate.Foo))), List(Match(EmptyTree, List())))))

Do you have any ideas about what could be causing this? Thanks!

Add License to the project

We found this library, to be useful in our project, can you add license, so we could use it, our management is very strict in this regards.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.