zio-json icon indicating copy to clipboard operation
zio-json copied to clipboard

seeing error "magnolia: could not find any direct subtypes of trait" when its not true!

Open cartazio opened this issue 4 years ago • 9 comments

The pattern seems to be when the base sealed trait has several parameters eg sealed trait Foo[A]

but the child class instantiates them

case class Bar(..) extends Foo[SomeConcreteType]

i'm hoping its other stuff that spuriously confusing zio-json, because overall i'm finding it to be a very humane tool! (though i did have to roll my own Map[k,v] instance to get some stuff to work )

cartazio avatar Feb 18 '21 16:02 cartazio

@cartazio I’m also wondering what the issue with Map[K, V] was that you ran into

fsvehla avatar Feb 18 '21 23:02 fsvehla

What was A that you tried to derive for?

joroKr21 avatar Feb 19 '21 08:02 joroKr21

a colleague and I will try to boil down a working reproducer that you can run yourself.

we're currently using the 0.1 release, if we can get a reproducer into your hands would that be helpful?

cartazio avatar Feb 19 '21 15:02 cartazio

import zio.json._

object Example {

  trait Bar[T] {
    def get(a: T): T
  }

  case class BarInstance() extends Bar[Int] {
    def get(a: Int): Int = a
  }

  case class FooTwoInstance[A <: Bar[Int]](
    `type`: Int
  ) extends FooTwo[A]

  case class FooInstance (
    fooTwo: FooTwo[BarInstance]
  ) extends Foo[BarInstance]

  sealed trait FooTwo[A <: Bar[Int]] {
    val `type`: Int
  }

  sealed trait Foo[A <: Bar[Int]] {
    val fooTwo: FooTwo[A]
  }

  implicit def decoder[A <: Bar[Int]](): JsonDecoder[Foo[A]] =
    DeriveJsonDecoder.gen[Foo[A]]
  implicit def encoder[A <: Bar[Int]](): JsonEncoder[Foo[A]] =
    DeriveJsonEncoder.gen[Foo[A]]
}

produces the following error:

magnolia: could not find any direct subtypes of trait Foo
    DeriveJsonDecoder.gen[Foo[A]]

And if we modify the decoder and encoder to no longer use type parameters like so:

  implicit def decoder(): JsonDecoder[Foo[BarInstance]] =
    DeriveJsonDecoder.gen[Foo[BarInstance]]
  implicit def encoder(): JsonEncoder[Foo[BarInstance]] =
    DeriveJsonEncoder.gen[Foo[BarInstance]]

we get the following error:

magnolia: could not find JsonDecoder.Typeclass for type Example.FooTwo[Example.BarInstance]
    in parameter 'fooTwo' of product type Example.FooInstance
    in coproduct type Example.Foo[Example.BarInstance]
    DeriveJsonDecoder.gen[Foo[BarInstance]]

agascon9 avatar Feb 19 '21 16:02 agascon9

thanks @agascon9 (my lovely colleague). this gives the essence of our problem (though it happens a few different ways).

and it actually nicely shows that the remaining challenges we have in our effort to migrate a code base to the joys of zio-json are one and the same (trying to fix our errors one way, pushes us to have errors another way ).

Any further ways we can help test/facilitate/validate a fix and subsequent release, we are at your service to enable and make happen. (the code base in question is large enough that moving to zio-json will transform the dev experience/build time for all our contributors, and help us generally do nice things)

cartazio avatar Feb 19 '21 16:02 cartazio

I guess we could hand write those instances as our near term fix, but i'm a tad leary of that :)

cartazio avatar Feb 19 '21 16:02 cartazio

Thanks for the minimisation, I assume this is rather a problem with magnolia itself. I can't recall how well it supports GADTs.

joroKr21 avatar Feb 19 '21 18:02 joroKr21

yeah, once i realized it was a funny scala gadt i was worried about that. At the very least, better errormessages about how to resolve it would be great. I think our fix ultimately is to just do the equivalent of DeriveJsonEncoder.gen[Foo[BarInstance]] sortah

cartazio avatar Feb 19 '21 19:02 cartazio

Currently jsoniter-scala-macros doesn't derive codecs for generic types, but it works fine for non-generic types:

import com.github.plokhotnyuk.jsoniter_scala.macros._
import com.github.plokhotnyuk.jsoniter_scala.core._

object Example {
  trait Bar[T] {
    def get(a: T): T
  }

  case class BarInstance() extends Bar[Int] {
    def get(a: Int): Int = a
  }

  case class FooTwoInstance[A <: Bar[Int]](`type`: Int) extends FooTwo[A]

  case class FooInstance(fooTwo: FooTwo[BarInstance]) extends Foo[BarInstance]

  sealed trait FooTwo[A <: Bar[Int]] {
    val `type`: Int
  }

  sealed trait Foo[A <: Bar[Int]] {
    val fooTwo: FooTwo[A]
  }

  implicit val codec: JsonValueCodec[Foo[BarInstance]] =
    JsonCodecMaker.make(CodecMakerConfig.withDiscriminatorFieldName(Some("hint")))

  def main(args: Array[String]): Unit = {
    val x: Foo[BarInstance] = FooInstance(FooTwoInstance(1))
    val json = writeToString(x)
    println(json)
    println(readFromString[Foo[BarInstance]](json))
  }
}

Bellow is an output, please check if it satisfies your expectations.

{"hint":"FooInstance","fooTwo":{"hint":"FooTwoInstance","type":1}}
FooInstance(FooTwoInstance(1))

Probably, primitive routines of jsoniter-scala-macros implementation can be easily ported to magnolia.

plokhotnyuk avatar Feb 19 '21 21:02 plokhotnyuk