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

How to implement union types / List[Either[A,B]] in Scala 2.13 ? #1139

Open
domdorn opened this issue Apr 26, 2024 · 1 comment
Open

How to implement union types / List[Either[A,B]] in Scala 2.13 ? #1139

domdorn opened this issue Apr 26, 2024 · 1 comment
Labels

Comments

@domdorn
Copy link

domdorn commented Apr 26, 2024

Hello!

I have a endpoint that streams a json-array (via akka json-streaming) (array, starting with [, then the elements, then ])
Usually, the stream returns elements of Type A (happy path). But the stream could also fail at one point and then return a single B as the last element and then the closing ].

I'm using Scala 2.13.
In Scala 3 I could use a union type to express this as List[A | B] ..
However, I'm currently stuck with scala 2.13, so as I cannot make A and B have the same supertype,
I thought about encoding this as a List[Either[A,B]] but this expects that the elements then
have a type field, e.g. its serializing the Either and I'm getting this error:

      Exception in thread "zio-fiber-120" com.github.plokhotnyuk.jsoniter_scala.core.JsonReaderException: expected key: "type", offset: 0x00000012, buf:
      +----------+-------------------------------------------------+------------------+
      |          |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f | 0123456789abcdef |
      +----------+-------------------------------------------------+------------------+
      | 00000000 | 5b 0a 7b 22 63 6f 72 72 65 6c 61 74 69 6f 6e 49 | [.{"correlationI |
      | 00000010 | 64 22 3a 22 30 30 30 30 30 30 30 30 2d 30 30 30 | d":"00000000-000 |
      | 00000020 | 30 2d 30 30 30 30 2d 30 30 30 30 2d 30 30 30 30 | 0-0000-0000-0000 |
      | 00000030 | 30 30 30 30 30 30 30 30 22 2c 22 65 72 72 6f 72 | 00000000","error |
      +----------+-------------------------------------------------+------------------+

I created a small sample which would encode this and expectedly its encoding the either like this:

val responseFailure: Failure = ScenarioValidationFailedFailure(
          correlationId,
          scenario1,
          List(
            JSR303ValidationFailedScenarioValidationFailure(
              "some failure",
              assetId,
              "some-name",
              AssetType.of("entities.speicher.Speicher"),
              Some(AssetAttributeId.of(2)),
              "STAUZIEL"))
        )

        val expectedError = VioptError(
          correlationId,
          ConcreteVioptError(
            VioptErrorCode.UnexpectedServerFailure,
            responseFailure.toString,
            masterdataServiceName,
            None
          ))
        val r: List[Either[VioptError, PublishScenario.PublishScenarioEvent]] = List(Right(PublishScenario.ScenarioPublished(correlationId, scenario1)), Left(expectedError))
        val json = com.github.plokhotnyuk.jsoniter_scala.core.writeToString(r)
        println(json)

returns this json

[{"type":"Right","value":{"type":"ScenarioPublished","correlationId":"00000000-0000-0000-0000-000000000000","scenarioId":1}},{"type":"Left","value":{"correlationId":"00000000-0000-0000-0000-000000000000","error":{"code":{"httpStatusCode":500,"name":"UnexpectedServerFailure"},"message":"ScenarioValidationFailedFailure(\n  correlationId: CorrelationId(00000000-0000-0000-0000-000000000000),\n  scenarioId: ScenarioId(1),\n  validationFailures: \nJSR303ValidationFailedScenarioValidationFailure(some failure,AssetId(10),some-name,AssetTypeImpl(entities.speicher.Speicher,false,false,List()),Some(AssetAttributeId(2)),STAUZIEL)\n)","service":"masterdata-service"}}}]

Is there any way to get the union type functionality in scala 2.13 ? e.g. a codec that first tries the JsonValueCodec[A] and if that fails tries JsonValueCodec[B] ?

Thanks!

@domdorn domdorn changed the title How to implement union types / List[Either[A,B]] How to implement union types / List[Either[A,B]] in Scala 2.13 ? Apr 26, 2024
@plokhotnyuk
Copy link
Owner

plokhotnyuk commented Apr 26, 2024

Hi Dominik,

Unfortunately, the current version of jsoniter-scala-macros doesn't support union types for Scala 3 and their simulation with Either[A, B] for Scala 2.

But you can write a custom codec that would be suitable for you case. The main challenge here is how to distinguish different types of A and B during JSON parsing efficiently. There are could be a couple of approaches with own pros and cons:

  1. If A types have the same required field that should not exists in B types then we can mark current position with in.setMark(), scan for that discriminating field using in.skipToKey("field-name"), rollback to the marked position with in.rollbackToMark() and then decode with provided codecs for A or B type depending on the result of the skipToKey call.

  2. If A types are less frequent than B then you can grab a raw values to a byte array with in.readRawValAsBytes() and then try to parse them as B in a try block catching a parsing error with subsequent re-parsing of that byte array as A type.

  3. If JSON values of A and B types start from different tokens like " and { for for String and some case class accordingly then the 1st value token could be used as a discriminator to parse with the custom codec in one pass, like here.

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