Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Global defaults for codecs #999

Open
mortenhornbech opened this issue Mar 1, 2023 · 7 comments
Open

Global defaults for codecs #999

mortenhornbech opened this issue Mar 1, 2023 · 7 comments
Labels

Comments

@mortenhornbech
Copy link

We have a number of configuration defaults we would like to reuse for multiple codecs, but when we factor them out into a variable we get the following compile error:

Cannot evaluate a parameter of the 'make' macro call for type 'String'. It should not depend on code from the same compilation module where the 'make' macro is called. Use a separated submodule of the project to compile all such dependencies before their usage for generation of codecs. Cause:
[error] java.lang.reflect.InvocationTargetException
[error] implicit val stringCodec: JsonValueCodec[String] = JsonCodecMaker.make(config)

What is more specifically required here. Does it need to be a completely seperate dependency where we store the config, or can we change the structure of the code somehow?

We could of course copy-paste the same configuration around, but that is difficult to maintain.

@plokhotnyuk
Copy link
Owner

plokhotnyuk commented Mar 1, 2023

You cannot reuse config as value or method call, but there is a couple of options:

  1. Use preconfigured make... calls without parameters
  2. Use derives keyword from Scala 3 like here
  3. Use some other macro that can generate the configuration expression in the place of the config parameter

Also, you can easily ask me for adding an additional make... call preconfigured for your needs, especially if your business would like to donate a bit.

@mortenhornbech
Copy link
Author

Thanks for quick reply! Generally we would like some more defensive defaults. For example we have had to make a lot of changes to javascript client code expecting empty collections and default values to be included. Maybe a parameter to specify preference for interoperability over json-size make sense. We are open to donations, but we need changes to be available on 2.13.5.x. Impressive framework you have build.

@plokhotnyuk
Copy link
Owner

plokhotnyuk commented Mar 1, 2023

Unfortunately, 2.13.5.x is closed for adding new methods, only backward and forward binary compatible patches are applicable.

It could be possible to bring Java 8 compatible code to mainline, but need to upgrade sbt-multi-release-jar plugin to use Java 11+ and to be able to work with a multi-platform build configuration.

I suppose that you use Scala 2 now, so please check if the 3rd option with a macro that generates the config parameter works for you:

Config generation macro

import com.github.plokhotnyuk.jsoniter_scala.macros._
import scala.language.experimental.macros
import scala.reflect.macros.blackbox.Context

object DefaultCodecMakerConfig {
  def gen: CodecMakerConfig = macro genImpl

  def genImpl(c: Context): c.universe.Tree = {
    import c.universe._

    q"""_root_.com.github.plokhotnyuk.jsoniter_scala.macros.CodecMakerConfig
          .withTransientEmpty(false)
          .withTransientDefault(false)
          .withTransientNone(false)"""
  }
}

Usage of config generation macro

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

case class DefaultCodecMakerConfigTest(d: String = "VVV", o: Option[Boolean] = None, s: Seq[Int] = Nil)

implicit val codec: JsonValueCodec[DefaultCodecMakerConfig] = JsonCodecMaker.make(DefaultCodecMakerConfig.gen)

assert(writeToString(DefaultCodecMakerConfigTest()) == """{"d":"VVV","o":null,"s":[]}""")

Beware that the generating macro should be compiled before its usage in a separated module.

@mortenhornbech
Copy link
Author

We use scala 2 yes. I will look into your suggestion later this week. Thanks!

@plokhotnyuk
Copy link
Owner

@mortenhornbech Have you had a chance to test the proposed approach of config generation macro?

@mortenhornbech
Copy link
Author

@plokhotnyuk Sorry, got caught up in urgent stuff. An issue is that our codecs already are located in our base module, so if I read your suggestion correctly, we would have to create a new base-base module for the sole purpose of this macro. And in that case do I then even need the macro? The original error message seems to suggest that I would be able to reference a shared variable in a base module.

@plokhotnyuk
Copy link
Owner

plokhotnyuk commented Mar 20, 2023

Yes, the macro in a separated module is only the option in Scala 2. For Scala 3 the macro is still required but it could be defined in the same module.

In the terse error message I tried to explain that name mapping functions which could be used for compile-time configuration with Scala 2 should be pure functions that do not depend even on static members of the context of definition. For Scala 3 limitations are harder, because built-in interpreter of name mapping functions in compile-time allows only simplest implementation of them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants