zio / zio-json

Fast, secure JSON library with tight ZIO integration.
https://zio.dev/zio-json
Apache License 2.0
410 stars 146 forks source link

Auto generate encoder of all nested case class #405

Open dev590t opened 3 years ago

dev590t commented 3 years ago

zio json can't generate encoder for nested case class:

        case class A(v: Int)
        case class B(v: A)
        object B {
          implicit val encoder: JsonEncoder[B] = 
            DeriveJsonEncoder.gen[B] // compilation fails
        }
        B(A(1)).toJson 

It is possible bypass the issue with sealed trait. But often, we can't change the case class defined in library. We haven't other solution that define encoder/decoder for all case class.

Is it possible to use scala macro to auto generate encoder of all nested case class?

aparo commented 3 years ago

This is not a good idea. The best practise is to have encoder/decoder in the compaign object.

        case class A(v: Int)
        object A {
          implicit val encoder: JsonEncoder[A] =  DeriveJsonEncoder.gen[A]
        }
        case class B(v: A)
        object B {
          implicit val encoder: JsonEncoder[B] =  DeriveJsonEncoder.gen[B]
        }
        B(A(1)).toJson 

Otherwise just simple add the derivation of A before B

        case class A(v: Int)
        case class B(v: A)
        object B {
          implicit val encoder: JsonEncoder[A] =  DeriveJsonEncoder.gen[A]
          implicit val encoder: JsonEncoder[B] =  DeriveJsonEncoder.gen[B]
        }
        B(A(1)).toJson 
dev590t commented 3 years ago

@aparo

The best practise is to have encoder/decoder in the compaign object.

However, I feel this solution is heavy. Especially, if we have many case class in the program. We should spend many time for the preparation before use zio json. Finally, the developer will write a mini program to collect all name of case class, and generate the code of encoder/decoder.

Maybe it is possible to add such mini program in the project? The program analyze the scala code, return the struct of a case class(with all dependant case class), and generate the code of of all encoder/decoder.

dev590t commented 3 years ago

zio-json is inspired from jsoniter-scala. But I have tested jsoniter-scala. It can generate encoder for nested case class:

{
    case class Device(id: Int, model: String)
    case class User(name: String, devices: Seq[Device])
    val userObject =
      (User(name = "John", devices = Seq(Device(id = 2, model = "iPhone X"))))
    suite("generate encoder for nested case class")(
      test("jsoniter-scala") {
        import com.github.plokhotnyuk.jsoniter_scala.macros._
        import com.github.plokhotnyuk.jsoniter_scala.core._
        implicit val codec: JsonValueCodec[User] = JsonCodecMaker.make
        val user = readFromArray(
          """{"name":"John","devices":[{"id":1,"model":"HTC One X"}]}"""
            .getBytes("UTF-8")
        )
        val json = writeToArray(userObject)
        assert(1)(equalTo(1))
      },
      test("zio-json") {
        import zio.json._
        implicit val encoder: JsonEncoder[User] = DeriveJsonEncoder.gen[User]
// magnolia: could not find JsonEncoder.Typeclass for type Seq[Device]
// [error]     in parameter 'devices' of product type User
// [error]         implicit val encoder: JsonEncoder[User] = DeriveJsonEncoder.gen[User]
        userObject.toJson
        assert(1)(equalTo(1))
      }
    )
  }

This code produce a compilation error for zio-json, no for jsoniter-scala.

I don't know how jsoniter-scala is implemented. Maybe it is possible to take idea from it?

maciejbak85 commented 3 years ago

Good idea! in my current microservices I had to resign from zio-json due to many nested case classes. Its very inconvenient to write 20 decoders/encoders just to operate on 1 case class.

guizmaii commented 1 year ago

@fsvehla I think we should close this issue as it's never a good idea to provide automagic instances. If people think it's too verbose, they should use the @jsonDerive macro. See https://zio.dev/zio-json/configuration#jsonderive