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

codegen: Jsoniter support (#840) #3610

Merged
merged 8 commits into from
Mar 18, 2024

Conversation

hughsimpson
Copy link
Contributor

@hughsimpson hughsimpson commented Mar 15, 2024

Starts to address #840

Adds support for jsoniter serdes in codegen.

Since, in order to support jsoniter, it became necessary to explicitly define the desired serdes, I've also moved from io.circe.generic.auto._ to io.circe.generic.semiauto._; the latter of which has the notable benefit that if it's going to fail to compile, it doesn't take until the heat death of the universe to do so.

I've also modified the model generation. When multipart file support was initially added, the generated type for format: binary strings was changed to sttp.model.Part[java.io.File], which notably does not work with json serialisation. In order to retain that support, I've added a check so that if a schema is not used in any application/json content we'll retain the File model, and otherwise use Array[Byte]. This means that a model defining a binary string type cannot be reused between multipart/form-data and application/json contexts, but I honestly have no idea how to avoid that limitation.

Extra edit: it turns out supporting adt serialisation with jsoniter-scala requires that the adt serdes be in a separate module file to the class declarations, so I'm immediately regretting this when trying to implement oneOf support but hey ho

@hughsimpson hughsimpson changed the title codegen: Jsoniter support codegen: Jsoniter support (#840) Mar 17, 2024
jsonParamRefs.toSeq.flatMap(ref => allSchemas.get(ref.stripPrefix("#/components/schemas/")))
)

val maybeJsonSerdeHelpers =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Shouldn't jsoniter-scala derive these automatically?

Copy link
Contributor Author

@hughsimpson hughsimpson Mar 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You would expect that, wouldn't you? I haven't really used jsoniter-scala much before so perhaps there's a less ham-fisted way to do this, but without these I get a lot of could not find implicit value for evidence parameter of type com.github.plokhotnyuk.jsoniter_scala.core.JsonValueCodec[List[sttp.tapir.generated.TapirGeneratedEndpoints.MyCaseClass]] and whatnot. My understanding is that it's expected that all top-level json formats will require an explicit serde declaration (and, honestly, I'm just relieved that the implicit defs work for the Option/List wrappers, so that we don't need to declare every single one of them individually)

Copy link
Contributor

@plokhotnyuk plokhotnyuk Mar 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is an ability to auto-derive codecs using an in-lined compile-time configuration, like here or here. But I'm not sure that it can be reusable here. Another approach taken by smithy4s is just defining all codecs for basic types and deriving for collections and complex ones like sum and product types: https://github.com/disneystreaming/smithy4s/blob/a5216184a5e584795c56a7a5a4def917849943f1/modules/json/src/smithy4s/json/internals/SchemaVisitorJCodec.scala

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm it works in a simple isolated experiment:

//>using dep "com.github.plokhotnyuk.jsoniter-scala:jsoniter-scala-core_3:2.28.4"
//>using dep "com.github.plokhotnyuk.jsoniter-scala:jsoniter-scala-macros_3:2.28.4"
import com.github.plokhotnyuk.jsoniter_scala.macros._
import com.github.plokhotnyuk.jsoniter_scala.core._

case class MySimpletype2(v: String)
case class MySimpleType(a: Int, b: List[MySimpletype2])

object Main extends App {
  implicit val codec: JsonValueCodec[MySimpleType] = com.github.plokhotnyuk.jsoniter_scala.macros.JsonCodecMaker.make[MySimpleType]
  val s = writeToString[MySimpleType](MySimpleType(1, List(MySimpletype2("a"))))
  println(s)
}

maybe it has something to do with location of the classes or the main CC codec 🤔

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or maybe it works only for Scala 3 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That example should work, but I think if you tried val s = writeToString[List[MySimpleType]](List(MySimpleType(1, List(MySimpletype2("a"))))) it would break. One thing I have only just appreciated is that jsoniter-scala doesn't need the intermediary serdes, so we needn't generate them for every schema model, only the top-level ones. Anyway, that's a different issue than the serdes defined here for primitive types, lists and options.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I understand now. Thanks for clarifying the difference @hughsimpson. The approach from Smithy4s looks interesting, thanks a lot for chiming in @plokhotnyuk.
I think I am OK with current solution, looks like a good start and it could be optimized in the future.

@kciesielski kciesielski merged commit 036e38b into softwaremill:master Mar 18, 2024
23 checks passed
@hughsimpson hughsimpson deleted the jsoniter_codegen_support branch March 18, 2024 12:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants