softwaremill / softwaremill/tapir

Module for JSON codec derivation

Open
#2,923 7 comments 0 reactions 1 assignee View on GitHub

@kciesielski is already working on this.

Since May 31, 2023.

  • #3134 by @kciesielski — merged
enhancement Pickler
Dominant language
Scala
Stars
1.5k
Forks
468
Avg merge
5h 37m
Merged PRs (30d)
34

Description

Currently, to create a json body input/output, both a Schema and json-library-specific encoders/decoders are needed. This means, that generic derivation is typically done twice (once for json encoders/decoders, once for schemas). Moreover, any customisations as to the naming strategy etc. need to be duplicated, often using different APIs, both for the json library and for the schemas.

It would be great to do the configuration and derivation once - but to do that, we would need to provide a module which would provide joint json encoder/decoder + tapir schema derivation. In other words, we would need to write code which derives a JsonCodec[T] (this includes the encode, decode and schema).

Doing this for all json libraries would be highly impractical, and a ton of work, for which we don't have resources. That's why I'd like to approach this using the json library that will be included in the Scala toolkit - that is, uPickle. uPickle can use a better derivation mechanism anyway (as our blogs have described), so it might be an additional win for our users.

Such a derivation would have to be written using a macro - and as we know, these are different in Scala 2/3. I think we should target Scala 3.

So summing up, the goal of the new module is to:

  • deliver a macro implementing generic derivation for a JsonCodec[T] for supported T types
  • the json implementation used should be uPickle
  • we are targeting Scala 3

While it might seem that the derivation could be implemented using Magnolia, I think writing a dedicated macro, which could utilize Scala 3's Mirrors, would actually be better. First, we would directly generate the code, instead of generating an intermediate representation, which is only converted to the final codec at run-time. That's a small performance win. But furthermore, we can provide better, contextual, error reporting. And excellent errors is something I'd like to be a priority for this task. I've done some experiments with deriving Schema using a macro directly here, but the work there has unfortunately stalled.

As for configuring the derivation, we should take into account the following:

  • customisations specified using Schema.annotatations on a perf-field/per-type basis - e.g. @encodedName should influence both the schema, and the generated json enoder/decoder
  • global customisations as specified in Configuration (global field name transformers etc.)
  • more options, than there are currently available through Configuration, to configure inheritance hierarchy serialization. This should include:
    • deserialisation using a discriminator field (partially available now) - with a value given with an annotation, or defaulting to the type's name
    • deserialisation using a single-field product (see Schema.oneOfWrapped)
    • deserialisation using a "first-successful" strategy
    • overriding the inheritance configuration locally using an annotation
    • maybe some more - to research what's available in other libraries
  • various options to serialise enumerations: as a string representation, as a result of function application, as an ordinal
  • adding annotations externally, e.g. through a list (class field, annotation value) pairs

In the end, the user should get an alternative to the current import sttp.tapir.json.upickle.* + optional imports for auto-deriving uPickles Reader/Writer & tapir's Schema; the alternative would define jsonBody etc. as the integration today, plus the macro to derive the JsonCodec.

Summing up, the top-level requirements for the macro are:

  • user-friendly error reports, clearly stating the derivation path that failed in case a codec for some nested type cannot be found
  • configurable derivation of inheritance strategies, naming strategies and enumeration handling
  • compile-time generation of the codec
  • drop-in replacement for the current uPickle integration
  • support for all Scala 3 types (enums, opaque, sum, intersection, etc.)

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.