0

I have a huge set of case classes where below is just a subset of these case classes:

sealed trait BuiltInType { def id: Int }
  case class ZombieType          (a: String,        id: Int = 0) extends BuiltInType
  case class BooleanType         (a: Boolean,       id: Int = 1) extends BuiltInType
  case class ByteType            (a: Byte,          id: Int = 2) extends BuiltInType
  case class UByteType           (a: Byte,          id: Int = 3) extends BuiltInType
  case class Int16Type           (a: Int,           id: Int = 4) extends BuiltInType
  case class UInt16Type          (a: Int,           id: Int = 5) extends BuiltInType
  case class Int32Type           (a: Int,           id: Int = 6) extends BuiltInType
  case class UInt32Type          (a: Int,           id: Int = 7) extends BuiltInType
  case class Int64Type           (a: Long,          id: Int = 8) extends BuiltInType
  case class UInt64Type          (a: Long,          id: Int = 9) extends BuiltInType
  case class FloatType           (a: Float,         id: Int = 10) extends BuiltInType
  case class DoubleType          (a: Double,        id: Int = 11) extends BuiltInType
  case class StringType          (a: String,        id: Int = 12) extends BuiltInType
  case class DateTimeType        (a: Long,          id: Int = 13) extends BuiltInType // FIXME: Wrong type used, fix it later
  case class GuidType            (a: UUID,          id: Int = 14) extends BuiltInType
  case class ByteStringType      (a: Vector[Byte],  id: Int = 15) extends BuiltInType
  case class XmlElementType      (a: String,        id: Int = 16) extends BuiltInType
  case class NodeIdType          (a: NodeId,        id: Int = 17) extends BuiltInType
  case class ExpandedNodeIdType  (a: NodeId,        id: Int = 18) extends BuiltInType // FIXME: Wrong type used, fix it later
  case class StatusCodeType      (a: StatusCode,    id: Int = 19) extends BuiltInType
  case class QualifiedNameType   (a: QualifiedName, id: Int = 20) extends BuiltInType
  case class LocalizedTextType   (a: LocalizedText, id: Int = 21) extends BuiltInType
  case class ExtensionObjectType (a: ExtensionObject, id: Int = 22) extends BuiltInType 
  case class DataValueType       (a: DataValue,       id: Int = 23) extends BuiltInType 
  case class VariantType         (a: Variant,       id: Int = 24) extends BuiltInType
  case class DiagnosticInfoType  (a: String,        id: Int = 25) extends BuiltInType 

As you can see that I have several other complex types defined. For example., there is a type called Variant which itself is a case class like this:

  sealed trait VariantData
  case class SimpleOrder(rows: Vector[BuiltInType]) extends VariantData
  case class HigherOrder(matrices: Vector[VariantData]) extends VariantData

  case class Variant(data: VariantData)

I then define all the needed implicit formats for Play JSON like this:

implicit val strType = Json.format[StringType]
implicit val guidType = Json.format[GuidType]
implicit val int16Type = Json.format[Int16Type]
implicit val uint64Type = Json.format[UInt64Type]
implicit val int32Type = Json.format[Int32Type]
implicit val int64Type = Json.format[Int64Type]
implicit val uByteTyp = Json.format[UByteType]
implicit val qNameType = Json.format[QualifiedNameType]
implicit val nodeIdTyp = Json.format[NodeIdType]
implicit val locTextTyp = Json.format[LocalizedTextType]
implicit val zombType = Json.format[ZombieType]
implicit val statusCodeTyp = Json.format[StatusCodeType]
implicit val builtInType = Json.format[BuiltInType]

implicit val simpleFmt = Json.format[SimpleOrder]
implicit val higherFmt = Json.format[HigherOrder]
implicit val varDataFmt = Json.format[VariantData]

implicit val varFmt = Json.format[Variant]

I get this error:

Error:(66, 43) No instance of Reads is available for domain.CommonTypes.VariantType in the implicit scope (Hint: if declared in the same file, make sure it's declared before)
    implicit val builtInType = Json.format[BuiltInType]

So I changed the order of implicits like this:

implicit val simpleFmt = Json.format[SimpleOrder]
implicit val higherFmt = Json.format[HigherOrder]
implicit val varDataFmt = Json.format[VariantData]

implicit val varFmt = Json.format[Variant]

implicit val strType = Json.format[StringType]
implicit val guidType = Json.format[GuidType]
implicit val int16Type = Json.format[Int16Type]
implicit val uint64Type = Json.format[UInt64Type]
implicit val int32Type = Json.format[Int32Type]
implicit val int64Type = Json.format[Int64Type]
implicit val uByteTyp = Json.format[UByteType]
implicit val qNameType = Json.format[QualifiedNameType]
implicit val nodeIdTyp = Json.format[NodeIdType]
implicit val locTextTyp = Json.format[LocalizedTextType]
implicit val zombType = Json.format[ZombieType]
implicit val statusCodeTyp = Json.format[StatusCodeType]
implicit val builtInType = Json.format[BuiltInType]

But now, I get this error:

Error:(53, 41) No instance of play.api.libs.json.Format is available for scala.collection.immutable.Vector[domain.CommonTypes.BuiltInType] in the implicit scope (Hint: if declared in the same file, make sure it's declared before)
    implicit val simpleFmt = Json.format[SimpleOrder]

So my question now is, is this an ordering problem or is there something that I'm missing as the complain here is that it is expecting a

Vector[BuiltInType]

Or is the cyclic dependency causing this problem?

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
joesan
  • 13,963
  • 27
  • 95
  • 232

2 Answers2

0

In general you can always fix problems with ordering like:

Put the formats in the companion-objects.

See the example below.

To simplify your work with sealed traits I would use play-json-derived-codecs

The imports:

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

The cool thing is you only need to format the trait, all its subclasses will be done automatically:

  implicit val jsonFormat: OFormat[VariantData] = derived.oformat[VariantData]()
  implicit val jsonFormat: OFormat[BuiltInType] = derived.oformat[BuiltInType]()

The format code is slightly different.

To illustrate how this simplifies the code here is Dmytro's example:

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

  sealed trait BuiltInType { def id: Int }
  case class StringType(a: String, id: Int = 12) extends BuiltInType    
  case class ByteStringType(a: Seq[Byte], id: Int = 15) extends BuiltInType    
  case class VariantType(a: Variant, id: Int = 24) extends BuiltInType

  object BuiltInType {
    implicit val jsonFormat: OFormat[BuiltInType] = derived.oformat[BuiltInType]()
  }
  sealed trait VariantData
  case class SimpleOrder(rows: Seq[BuiltInType]) extends VariantData
  case class HigherOrder(matrices: Seq[VariantData]) extends VariantData

  object VariantData {
    implicit val jsonFormat: OFormat[VariantData] = derived.oformat[VariantData]()
  }

  case class Variant(data: VariantData)

  object Variant {
    implicit val jsonFormat: OFormat[Variant] = derived.oformat[Variant]()
  }

  val json = Json.toJson(VariantType(Variant(SimpleOrder(Seq(StringType("aaa"))))): BuiltInType)
  println(json) //{"a":{"data":{"_type":"App.SimpleOrder","rows":[{"a":"aaa","_type":"App.StringType","id":12}]}},"_type":"App.VariantType","id":24}

With your example the difference will be even bigger.

pme
  • 14,156
  • 3
  • 52
  • 95
  • There is no need for extra lib about sealed trait already supported by play-json itself – cchantep Aug 27 '19 at 22:13
  • 1
    @cchantep thanks - but do you also not have to declare format for each Subclass? – pme Aug 28 '19 at 05:50
  • Where do I find the library and which version should I use? – joesan Aug 30 '19 at 10:34
  • 1
    In my answer there is the link to the library. The version depends on your Play version. I use 5.0.0 with Play 2.7. – pme Sep 01 '19 at 06:37
  • I do not use Play as my web framework, but use Akka HTTP. Could I still use this library? – joesan Sep 06 '19 at 09:44
  • I think so - Play also uses Akka HTTP (2.7) – pme Sep 06 '19 at 09:46
  • I have another question on the ordering which I have asked here: https://stackoverflow.com/questions/57820366/ordering-of-fileds-on-the-generated-json-using-play-json-derived-codecs – joesan Sep 06 '19 at 10:33
0

Don't forget to specify types of implicits and re-arrange implicits so that there are no warnings

Warning: Reference to uninitialized value ... 
  implicit val ...

The following code seems to work in Scala 2.13.0/2.12.8 + play-json 2.7.4

import play.api.libs.json._

object App {
  sealed trait BuiltInType { def id: Int }
  case class StringType          (a: String,        id: Int = 12) extends BuiltInType
  case class ByteStringType      (a: Vector[Byte],  id: Int = 15) extends BuiltInType
  case class VariantType         (a: Variant,       id: Int = 24) extends BuiltInType

  sealed trait VariantData
  case class SimpleOrder(rows: Vector[BuiltInType]) extends VariantData
  case class HigherOrder(matrices: Vector[VariantData]) extends VariantData

  case class Variant(data: VariantData)

  implicit val strType: OFormat[StringType] = Json.format[StringType]
  implicit val byteStrType: OFormat[ByteStringType] = Json.format[ByteStringType]    
  implicit val varDataFmt: OFormat[VariantData] = Json.format[VariantData]
  implicit val builtInType: OFormat[BuiltInType] = Json.format[BuiltInType]
  implicit val simpleFmt: OFormat[SimpleOrder] = Json.format[SimpleOrder]
  implicit val higherFmt: OFormat[HigherOrder] = Json.format[HigherOrder]
  implicit val varFmt: OFormat[Variant] = Json.format[Variant]
  implicit val varType: OFormat[VariantType] = Json.format[VariantType]

  val json = Json.toJson(VariantType(Variant(SimpleOrder(Vector(StringType("aaa"))))): BuiltInType)
  println(json)//{"a":{"data":{"_type":"App.SimpleOrder","rows":[{"a":"aaa","_type":"App.StringType","id":12}]}},"_type":"App.VariantType","id":24}
}
Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • Where did you declare a format for Variant? How is that even compiling? I still can't get it to compile! – joesan Aug 28 '19 at 04:17
  • @sparkr Fixed (I accidentally missed last two implicits `varFmt` and `varType` in my answer). Try now. – Dmytro Mitin Aug 28 '19 at 05:38
  • It still fails with the error as mentioned in my other post here: https://stackoverflow.com/questions/57685264/play-json-error-with-many-nested-case-classes?noredirect=1#comment101822841_57685264 – joesan Aug 28 '19 at 16:54
  • Can you take a look into the project and let me know how this could be fixed? I have given the link to the GitHub project here: https://stackoverflow.com/questions/57685264/play-json-error-with-many-nested-case-classes?noredirect=1#comment101822841_57685264 – joesan Aug 28 '19 at 16:55
  • @sparkr Sorry, I don't have ready solution for the whole project at Github. This is too much work to do. I can see that you still don't annotate implicits with types. It's possible that not all necessary implicits are defined and some implicits are badly ordered. I would try firstly to work with smaller subset of implicits in order to localize the issue. I guess trouble is somewhere among implicits `higherFmt varDataFmt varFmt dataValueType dataValTyp kvFmt fieldMtdFmt dataSetMetaDataFmt variantType builtInType`. – Dmytro Mitin Aug 30 '19 at 08:15
  • @sparkr Also it's possible that the thing is that types for which the type class is derived are recursive so some implicits should be defined lazily like `implicit val simpleFmt: OFormat[SimpleOrder] = (__ \ "rows").lazyFormat(Reads.seq[BuiltInType], Writes.seq[BuiltInType]) .inmap(s => SimpleOrder(s.toVector), (_: SimpleOrder).rows)`. Also I'd try `play-json-derived-codecs` as @pme advised. This can decrease number of implicits. – Dmytro Mitin Aug 30 '19 at 08:15