# Custom Recursive Schemas

**URL:** <https://softwaremill.community/t/custom-recursive-schemas/419>\
**Category:** tapir\
**Created:** [July 10, 2024, 12:36pm UTC](https://softwaremill.community/t/custom-recursive-schemas/419 "2024-07-10T12:36:32Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![lgmyrek](https://dub1.discourse-cdn.com/flex005/user_avatar/softwaremill.community/lgmyrek/32/272_2.png) [@lgmyrek](https://softwaremill.community/u/lgmyrek)\
**Post date:** [July 10, 2024, 12:36pm UTC](https://softwaremill.community/t/custom-recursive-schemas/419/1 "2024-07-10T12:36:32Z")

</div>

Let’s assume I need to make some kind of custom coproduct schema that is also recursive and it is neither wrapped nor it has discriminator - let’s say for API compatibility

example:

> **[Scastie - An interactive playground for Scala.](https://scastie.scala-lang.org/fcg455NGSOyGlKstCLjoYw)**
>
> object Defs {
> type Tree\[A\] = A Or Node\[A\]
> 
> final case class Node\[A\](vals: List\[Tree\[A\]\])
> 
> sealed trait Or\[+A, +B\]
> final case class OrLeft\[+A, +B\](left: A) extends Or\[A, B\]
> final case class OrRight\[+A, +B\](right: B) extends Or\[A, B\]
> 
> final...

model

```scala
  type Tree[A] = A Or Node[A]

  final case class Node[A](vals: List[Tree[A]])

  sealed trait Or[+A, +B]
  final case class OrLeft[+A, +B](left: A) extends Or[A, B]
  final case class OrRight[+A, +B](right: B) extends Or[A, B]

  final case class WrappedString(value: String)
  final case class WrappedInt(value: Int)

```

what I want to achieve is a schema that is oneOf: [A, Node[A]]

* * *

because it is recursive I ASSUME I need to handle it with refs @line - 42

tho because I am handling it with refs it seems that the schema for Node is not generated / picked up by tapir and we get not resolved ref for that schema.

this is solvable by:

```scala
  val dummyEndpoints = endpoint
    .get
    .name("dummy endpoint to make docs work")
    .in("dummy")
    .in(jsonBody[Node[WrappedString]])
    .in(jsonBody[Node[WrappedInt]])

```

but welp I do not really want to be doing that 😄

so:

1. is there a better way to handle custom recursive coproduct schemas?
2. is that need for dummyEndpoint expected / solvable in a better way

---

<div class="post-metadata">

**Author:** ![kciesielski](https://dub1.discourse-cdn.com/flex005/user_avatar/softwaremill.community/kciesielski/32/133_2.png) [@kciesielski](https://softwaremill.community/u/kciesielski)\
**Post date:** [July 10, 2024, 2:59pm UTC](https://softwaremill.community/t/custom-recursive-schemas/419/2 "2024-07-10T14:59:07Z")

</div>

@lgmyrek thanks for the Scastie, it’s really helpful 🙂 The model indeed looks tricky, could you show a few examples of valid JSONs? I assume you need to adjust to some existing API?

---

<div class="post-metadata">

**Author:** ![lgmyrek](https://dub1.discourse-cdn.com/flex005/user_avatar/softwaremill.community/lgmyrek/32/272_2.png) [@lgmyrek](https://softwaremill.community/u/lgmyrek)\
**Post date:** [July 10, 2024, 3:39pm UTC](https://softwaremill.community/t/custom-recursive-schemas/419/3 "2024-07-10T15:39:33Z")

</div>

let’s assume that WrappedInt and WrappedString have deserializers and schemas that produce primitive types instead of this

```auto
{
  "value": 1
}

```

for the sake os slightly more readable example

* * *

so endpoint1 COULD consume JUST value

```auto
3

```

OR tree

```auto
{
  "vals": [
    3,
    4,
    {
      "vals": [
        {
          "vals": [5]
        },
        6,
        7
      ]
    },
    8
  ]
}

```

* * *

if you replace all primitive ints in that example to:

```auto
{
  "value": $int
}

```

then that example will match scastie

---

<div class="post-metadata">

**Author:** ![lgmyrek](https://dub1.discourse-cdn.com/flex005/user_avatar/softwaremill.community/lgmyrek/32/272_2.png) [@lgmyrek](https://softwaremill.community/u/lgmyrek)\
**Post date:** [July 10, 2024, 3:48pm UTC](https://softwaremill.community/t/custom-recursive-schemas/419/4 "2024-07-10T15:48:34Z")

</div>

we could implement such encoder / decoder in eg circe somewhat like:

```scala
implicit def decoderForAOrNodeA[A: Decoder]: Decoder[A Or Node[A]] = 
  Decoder[A].map(OrLeft).or(Decoder[Node[A]].map(OrRight))

implicit def encoderForAOrNodeA[A: Encoder]: Encoder[A Or Node[A]] = {
  case OrLeft(a) => a.asJson
  case OrRight(aNode) => aNode.asJson
}

```

---

<div class="post-metadata">

**Author:** ![lgmyrek](https://dub1.discourse-cdn.com/flex005/user_avatar/softwaremill.community/lgmyrek/32/272_2.png) [@lgmyrek](https://softwaremill.community/u/lgmyrek)\
**Post date:** [July 11, 2024, 2:21pm UTC](https://softwaremill.community/t/custom-recursive-schemas/419/5 "2024-07-11T14:21:47Z")

</div>

@kciesielski I’ve found yet another issue that happens with extended version of this example.

> **[Scastie - An interactive playground for Scala.](https://scastie.scala-lang.org/anwau101QxK0XfOZ8TeK9w)**
>
> object Defs {
> type Tree\[A\] = A Or Nodes\[A\]
> 
> sealed trait Nodes\[A\]
> final case class Node\[A\](vals: List\[Tree\[A\]\]) extends Nodes\[A\]
> 
> sealed trait Or\[+A, +B\]
> final case class OrLeft\[+A, +B\](left: A) extends Or\[A, B\]
> final case class...

```auto
    Nodes_WrappedString:
      title: Nodes_WrappedString
      oneOf:
      - $ref: '#/components/schemas/Node_WrappedString'
      discriminator:
        propertyName: type
        mapping:
          Node: Defs.Node

```

has proper reference in oneOf, but moments later breaks on mapping.

dummy hack no longer fixes it.

Want me to create an issue on tapir for those?

---

<div class="post-metadata">

**Author:** ![kciesielski](https://dub1.discourse-cdn.com/flex005/user_avatar/softwaremill.community/kciesielski/32/133_2.png) [@kciesielski](https://softwaremill.community/u/kciesielski)\
**Post date:** [July 12, 2024, 7:00am UTC](https://softwaremill.community/t/custom-recursive-schemas/419/6 "2024-07-12T07:00:24Z")

</div>

Yes, it would be great if you could create one for the mapping.
