All Downloads are FREE. Search and download functionalities are using the official Maven repository.

smithy4s.json.internals.SchemaVisitorJCodec.scala Maven / Gradle / Ivy

There is a newer version: 0.19.0-41-91762fb
Show newest version
/*
 *  Copyright 2021-2024 Disney Streaming
 *
 *  Licensed under the Tomorrow Open Source Technology License, Version 1.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *     https://disneystreaming.github.io/TOST-1.0.txt
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package smithy4s
package json
package internals

import java.util.UUID
import java.util

import com.github.plokhotnyuk.jsoniter_scala.core.JsonReader
import com.github.plokhotnyuk.jsoniter_scala.core.JsonWriter
import smithy.api.JsonName
import smithy.api.TimestampFormat
import alloy.Discriminated
import alloy.Nullable
import alloy.Untagged
import smithy4s.internals.DiscriminatedUnionMember
import smithy4s.schema._
import smithy4s.schema.Primitive._
import smithy4s.Timestamp

import scala.collection.compat.immutable.ArraySeq
import scala.collection.immutable.VectorBuilder
import scala.collection.mutable.ListBuffer
import scala.collection.mutable.{Map => MMap}
import scala.collection.immutable.ListMap

private[smithy4s] class SchemaVisitorJCodec(
    maxArity: Int,
    explicitDefaultsEncoding: Boolean,
    infinitySupport: Boolean,
    flexibleCollectionsSupport: Boolean,
    preserveMapOrder: Boolean,
    lenientTaggedUnionDecoding: Boolean,
    val cache: CompilationCache[JCodec]
) extends SchemaVisitor.Cached[JCodec] { self =>
  private val emptyMetadata: MMap[String, Any] = MMap.empty

  object PrimitiveJCodecs {
    val boolean: JCodec[Boolean] =
      new JCodec[Boolean] {
        def expecting: String = "boolean"

        def decodeValue(cursor: Cursor, in: JsonReader): Boolean =
          in.readBoolean()

        def encodeValue(x: Boolean, out: JsonWriter): Unit = out.writeVal(x)

        def decodeKey(in: JsonReader): Boolean = in.readKeyAsBoolean()

        def encodeKey(x: Boolean, out: JsonWriter): Unit = out.writeKey(x)
      }

    val string: JCodec[String] =
      new JCodec[String] {
        def expecting: String = "string"

        def decodeValue(cursor: Cursor, in: JsonReader): String =
          in.readString(null)

        def encodeValue(x: String, out: JsonWriter): Unit = out.writeVal(x)

        def decodeKey(in: JsonReader): String = in.readKeyAsString()

        def encodeKey(x: String, out: JsonWriter): Unit = out.writeKey(x)
      }

    val int: JCodec[Int] =
      new JCodec[Int] {
        def expecting: String = "int"

        def decodeValue(cursor: Cursor, in: JsonReader): Int = in.readInt()

        def encodeValue(x: Int, out: JsonWriter): Unit = out.writeVal(x)

        def decodeKey(in: JsonReader): Int = in.readKeyAsInt()

        def encodeKey(x: Int, out: JsonWriter): Unit = out.writeKey(x)
      }

    val long: JCodec[Long] =
      new JCodec[Long] {
        def expecting: String = "long"

        def decodeValue(cursor: Cursor, in: JsonReader): Long = in.readLong()

        def encodeValue(x: Long, out: JsonWriter): Unit = out.writeVal(x)

        def decodeKey(in: JsonReader): Long = in.readKeyAsLong()

        def encodeKey(x: Long, out: JsonWriter): Unit = out.writeKey(x)
      }

    private val efficientFloat: JCodec[Float] =
      new JCodec[Float] {
        def expecting: String = "float"

        def decodeValue(cursor: Cursor, in: JsonReader): Float = in.readFloat()

        def encodeValue(x: Float, out: JsonWriter): Unit = out.writeVal(x)

        def decodeKey(in: JsonReader): Float = in.readKeyAsFloat()

        def encodeKey(x: Float, out: JsonWriter): Unit = out.writeKey(x)
      }

    private val infinityAllowingFloat: JCodec[Float] = new JCodec[Float] {
      val expecting: String = "JSON number for numeric values"

      def decodeValue(cursor: Cursor, in: JsonReader): Float =
        if (in.isNextToken('"')) {
          in.rollbackToken()
          val len = in.readStringAsCharBuf()
          if (in.isCharBufEqualsTo(len, "NaN")) Float.NaN
          else if (in.isCharBufEqualsTo(len, "Infinity")) Float.PositiveInfinity
          else if (in.isCharBufEqualsTo(len, "-Infinity"))
            Float.NegativeInfinity
          else in.decodeError("illegal float")
        } else {
          in.rollbackToken()
          in.readFloat()
        }

      def encodeValue(f: Float, out: JsonWriter): Unit =
        if (java.lang.Float.isFinite(f)) out.writeVal(f)
        else
          out.writeNonEscapedAsciiVal {
            if (f != f) "NaN"
            else if (f >= 0) "Infinity"
            else "-Infinity"
          }

      def decodeKey(in: JsonReader): Float = ???

      def encodeKey(x: Float, out: JsonWriter): Unit = ???
    }

    val float: JCodec[Float] =
      if (infinitySupport) infinityAllowingFloat else efficientFloat

    private val efficientDouble: JCodec[Double] =
      new JCodec[Double] {
        def expecting: String = "double"

        def decodeValue(cursor: Cursor, in: JsonReader): Double =
          in.readDouble()

        def encodeValue(x: Double, out: JsonWriter): Unit = out.writeVal(x)

        def decodeKey(in: JsonReader): Double = in.readKeyAsDouble()

        def encodeKey(x: Double, out: JsonWriter): Unit = out.writeKey(x)
      }

    private val infinityAllowingDouble: JCodec[Double] = new JCodec[Double] {
      val expecting: String = "JSON number for numeric values"

      def decodeValue(cursor: Cursor, in: JsonReader): Double =
        if (in.isNextToken('"')) {
          in.rollbackToken()
          val len = in.readStringAsCharBuf()
          if (in.isCharBufEqualsTo(len, "NaN")) Double.NaN
          else if (in.isCharBufEqualsTo(len, "Infinity"))
            Double.PositiveInfinity
          else if (in.isCharBufEqualsTo(len, "-Infinity"))
            Double.NegativeInfinity
          else in.decodeError("illegal double")
        } else {
          in.rollbackToken()
          in.readDouble()
        }

      def encodeValue(d: Double, out: JsonWriter): Unit =
        if (java.lang.Double.isFinite(d)) out.writeVal(d)
        else
          out.writeNonEscapedAsciiVal {
            if (d != d) "NaN"
            else if (d >= 0) "Infinity"
            else "-Infinity"
          }

      def decodeKey(in: JsonReader): Double = ???

      def encodeKey(x: Double, out: JsonWriter): Unit = ???
    }

    val double: JCodec[Double] =
      if (infinitySupport) infinityAllowingDouble else efficientDouble

    val short: JCodec[Short] =
      new JCodec[Short] {
        def expecting: String = "short"

        def decodeValue(cursor: Cursor, in: JsonReader): Short = in.readShort()

        def encodeValue(x: Short, out: JsonWriter): Unit = out.writeVal(x)

        def decodeKey(in: JsonReader): Short = in.readKeyAsShort()

        def encodeKey(x: Short, out: JsonWriter): Unit = out.writeKey(x)
      }

    val byte: JCodec[Byte] =
      new JCodec[Byte] {
        def expecting: String = "byte"

        def decodeValue(cursor: Cursor, in: JsonReader): Byte = in.readByte()

        def encodeValue(x: Byte, out: JsonWriter): Unit = out.writeVal(x)

        def decodeKey(in: JsonReader): Byte = in.readKeyAsByte()

        def encodeKey(x: Byte, out: JsonWriter): Unit = out.writeKey(x)
      }

    val bytes: JCodec[Blob] =
      new JCodec[Blob] {
        def expecting: String = "byte-array" // or blob?

        override def canBeKey: Boolean = false

        def decodeValue(cursor: Cursor, in: JsonReader): Blob = Blob(
          in.readBase64AsBytes(null)
        )

        def encodeValue(x: Blob, out: JsonWriter): Unit =
          out.writeBase64Val(x.toArray, doPadding = true)

        def decodeKey(in: JsonReader): Blob =
          in.decodeError("Cannot use byte array as key")

        def encodeKey(x: Blob, out: JsonWriter): Unit =
          out.encodeError("Cannot use byte array as key")
      }

    val bigdecimal: JCodec[BigDecimal] =
      new JCodec[BigDecimal] {
        def expecting: String = "big-decimal"

        def decodeValue(cursor: Cursor, in: JsonReader): BigDecimal =
          in.readBigDecimal(null)

        def decodeKey(in: JsonReader): BigDecimal = in.readKeyAsBigDecimal()

        def encodeValue(value: BigDecimal, out: JsonWriter): Unit =
          out.writeVal(value)

        def encodeKey(value: BigDecimal, out: JsonWriter): Unit =
          out.writeVal(value)
      }

    val bigint: JCodec[BigInt] =
      new JCodec[BigInt] {
        def expecting: String = "big-int"

        def decodeValue(cursor: Cursor, in: JsonReader): BigInt =
          in.readBigInt(null)

        def decodeKey(in: JsonReader): BigInt = in.readKeyAsBigInt()

        def encodeValue(value: BigInt, out: JsonWriter): Unit =
          out.writeVal(value)

        def encodeKey(value: BigInt, out: JsonWriter): Unit =
          out.writeVal(value)
      }

    val uuid: JCodec[UUID] =
      new JCodec[UUID] {
        def expecting: String = "uuid"

        def decodeValue(cursor: Cursor, in: JsonReader): UUID =
          in.readUUID(null)

        def encodeValue(x: UUID, out: JsonWriter): Unit = out.writeVal(x)

        def decodeKey(in: JsonReader): UUID = in.readKeyAsUUID()

        def encodeKey(x: UUID, out: JsonWriter): Unit = out.writeKey(x)
      }

    val timestampDateTime: JCodec[Timestamp] = new JCodec[Timestamp] {
      val expecting: String = Timestamp.showFormat(TimestampFormat.DATE_TIME)

      def decodeValue(cursor: Cursor, in: JsonReader): Timestamp =
        Timestamp.parse(in.readString(null), TimestampFormat.DATE_TIME) match {
          case x: Some[Timestamp] => x.get
          case _                  => in.decodeError("expected " + expecting)
        }

      def encodeValue(x: Timestamp, out: JsonWriter): Unit =
        out.writeNonEscapedAsciiVal(x.format(TimestampFormat.DATE_TIME))

      def decodeKey(in: JsonReader): Timestamp =
        Timestamp.parse(in.readKeyAsString(), TimestampFormat.DATE_TIME) match {
          case x: Some[Timestamp] => x.get
          case _                  => in.decodeError("expected " + expecting)
        }

      def encodeKey(x: Timestamp, out: JsonWriter): Unit =
        out.writeNonEscapedAsciiKey(x.format(TimestampFormat.DATE_TIME))
    }

    val timestampHttpDate: JCodec[Timestamp] = new JCodec[Timestamp] {
      val expecting: String = Timestamp.showFormat(TimestampFormat.HTTP_DATE)

      def decodeValue(cursor: Cursor, in: JsonReader): Timestamp =
        Timestamp.parse(in.readString(null), TimestampFormat.HTTP_DATE) match {
          case x: Some[Timestamp] => x.get
          case _                  => in.decodeError("expected " + expecting)
        }

      def encodeValue(x: Timestamp, out: JsonWriter): Unit =
        out.writeNonEscapedAsciiVal(x.format(TimestampFormat.HTTP_DATE))

      def decodeKey(in: JsonReader): Timestamp =
        Timestamp.parse(in.readKeyAsString(), TimestampFormat.HTTP_DATE) match {
          case x: Some[Timestamp] => x.get
          case _                  => in.decodeError("expected " + expecting)
        }

      def encodeKey(x: Timestamp, out: JsonWriter): Unit =
        out.writeNonEscapedAsciiKey(x.format(TimestampFormat.HTTP_DATE))
    }

    val timestampEpochSeconds: JCodec[Timestamp] = new JCodec[Timestamp] {
      val expecting: String =
        Timestamp.showFormat(TimestampFormat.EPOCH_SECONDS)

      def decodeValue(cursor: Cursor, in: JsonReader): Timestamp = {
        val timestamp = in.readBigDecimal(null)
        val epochSecond = timestamp.toLong
        Timestamp(epochSecond, ((timestamp - epochSecond) * 1000000000).toInt)
      }

      def encodeValue(x: Timestamp, out: JsonWriter): Unit = {
        if (x.nano == 0) {
          out.writeVal(x.epochSecond)
        } else {
          out.writeVal(BigDecimal(x.epochSecond) + x.nano / 1000000000.0)
        }
      }

      def decodeKey(in: JsonReader): Timestamp = {
        val timestamp = in.readKeyAsBigDecimal()
        val epochSecond = timestamp.toLong
        Timestamp(epochSecond, ((timestamp - epochSecond) * 1000000000).toInt)
      }

      def encodeKey(x: Timestamp, out: JsonWriter): Unit =
        out.writeKey(BigDecimal(x.epochSecond) + x.nano / 1000000000.0)
    }

    val unit: JCodec[Unit] =
      new JCodec[Unit] {
        def expecting: String = "empty object"

        override def canBeKey: Boolean = false

        def decodeValue(cursor: Cursor, in: JsonReader): Unit =
          if (!in.isNextToken('{') || !in.isNextToken('}'))
            in.decodeError("Expected empty object")

        def encodeValue(x: Unit, out: JsonWriter): Unit = {
          out.writeObjectStart()
          out.writeObjectEnd()
        }

        def decodeKey(in: JsonReader): Unit =
          in.decodeError("Cannot use Unit as keys")

        def encodeKey(x: Unit, out: JsonWriter): Unit =
          out.encodeError("Cannot use Unit as keys")
      }

    def document(maxArity: Int): JCodec[Document] = new JCodec[Document] {
      import Document._
      override def canBeKey: Boolean = false

      def encodeValue(doc: Document, out: JsonWriter): Unit = doc match {
        case s: DString  => out.writeVal(s.value)
        case b: DBoolean => out.writeVal(b.value)
        case n: DNumber  => out.writeVal(n.value)
        case a: DArray =>
          out.writeArrayStart()
          a.value match {
            // short-circuiting on empty arrays to avoid the downcast to array of documents
            // which has proven to be dangerous in Scala 3:
            // https://github.com/disneystreaming/smithy4s/issues/1158
            case x: ArraySeq[_] =>
              if (x.isEmpty) ()
              else {
                val xs = x.unsafeArray.asInstanceOf[Array[Document]]
                var i = 0
                while (i < xs.length) {
                  encodeValue(xs(i), out)
                  i += 1
                }
              }
            case xs =>
              xs.foreach(encodeValue(_, out))
          }
          out.writeArrayEnd()
        case o: DObject =>
          out.writeObjectStart()
          o.value.foreach { kv =>
            out.writeKey(kv._1)
            encodeValue(kv._2, out)
          }
          out.writeObjectEnd()
        case _ => out.writeNull()
      }

      def decodeKey(in: JsonReader): Document =
        in.decodeError("Cannot use JSON document as keys")

      def encodeKey(x: Document, out: JsonWriter): Unit =
        out.encodeError("Cannot use JSON documents as keys")

      def expecting: String = "JSON document"

      // Borrowed from: https://github.com/plokhotnyuk/jsoniter-scala/blob/e80d51019b39efacff9e695de97dce0c23ae9135/jsoniter-scala-benchmark/src/main/scala/io/circe/CirceJsoniter.scala
      def decodeValue(cursor: Cursor, in: JsonReader): Document = {
        val b = in.nextToken()
        if (b == '"') {
          in.rollbackToken()
          new DString(in.readString(null))
        } else if (b == 'f' || b == 't') {
          in.rollbackToken()
          new DBoolean(in.readBoolean())
        } else if ((b >= '0' && b <= '9') || b == '-') {
          in.rollbackToken()
          new DNumber(in.readBigDecimal(null))
        } else if (b == '[') {
          new DArray({
            if (in.isNextToken(']')) ArraySeq.empty[Document]
            else
              ArraySeq.unsafeWrapArray {
                in.rollbackToken()
                var arr = new Array[Document](4)
                var i = 0
                while ({
                  if (i >= maxArity) maxArityError(cursor)
                  if (i == arr.length)
                    arr = java.util.Arrays.copyOf(arr, i << 1)
                  arr(i) = decodeValue(in, null)
                  i += 1
                  in.isNextToken(',')
                }) {}
                if (in.isCurrentToken(']')) {
                  if (i == arr.length) arr
                  else java.util.Arrays.copyOf(arr, i)
                } else in.arrayEndOrCommaError()
              }
          })
        } else if (b == '{') {
          new DObject({
            if (in.isNextToken('}')) Map.empty
            else {
              in.rollbackToken()
              // We use the maxArity limit to mitigate DoS vulnerability in default Scala `Map` implementation: https://github.com/scala/bug/issues/11203
              val obj =
                if (preserveMapOrder) ListMap.newBuilder[String, Document]
                else Map.newBuilder[String, Document]
              var i = 0
              while ({
                if (i >= maxArity) maxArityError(cursor)
                obj += ((in.readKeyAsString(), decodeValue(in, null)))
                i += 1
                in.isNextToken(',')
              }) {}
              if (in.isCurrentToken('}')) obj.result()
              else in.objectEndOrCommaError()
            }
          })
        } else in.readNullOrError(DNull, "expected JSON document")
      }

      private def maxArityError(cursor: Cursor): Nothing =
        throw cursor.payloadError(
          this,
          s"Input $expecting exceeded max arity of $maxArity"
        )
    }
  }

  private val documentJCodec = PrimitiveJCodecs.document(maxArity)
  override def primitive[P](
      shapeId: ShapeId,
      hints: Hints,
      tag: Primitive[P]
  ): JCodec[P] = {
    tag match {
      case PBigDecimal => PrimitiveJCodecs.bigdecimal
      case PBigInt     => PrimitiveJCodecs.bigint
      case PBlob       => PrimitiveJCodecs.bytes
      case PBoolean    => PrimitiveJCodecs.boolean
      case PByte       => PrimitiveJCodecs.byte
      case PDocument   => documentJCodec
      case PDouble     => PrimitiveJCodecs.double
      case PFloat      => PrimitiveJCodecs.float
      case PInt        => PrimitiveJCodecs.int
      case PLong       => PrimitiveJCodecs.long
      case PShort      => PrimitiveJCodecs.short
      case PString     => PrimitiveJCodecs.string
      case PTimestamp  => timestampJCodec(hints)

      case PUUID => PrimitiveJCodecs.uuid
    }
  }

  def timestampJCodec(
      hints: Hints,
      defaultTimestamp: TimestampFormat = TimestampFormat.EPOCH_SECONDS
  ): JCodec[Timestamp] = {
    hints.get(TimestampFormat).getOrElse(defaultTimestamp) match {
      case TimestampFormat.DATE_TIME => PrimitiveJCodecs.timestampDateTime
      case TimestampFormat.EPOCH_SECONDS =>
        PrimitiveJCodecs.timestampEpochSeconds
      case TimestampFormat.HTTP_DATE => PrimitiveJCodecs.timestampHttpDate
    }
  }

  private def listImpl[A](member: Schema[A]) = new JCodec[List[A]] {
    private[this] val a: JCodec[A] = apply(member)

    def expecting: String = "list"

    override def canBeKey: Boolean = false

    def decodeValue(cursor: Cursor, in: JsonReader): List[A] =
      if (in.isNextToken('[')) {
        if (in.isNextToken(']')) Nil
        else {
          in.rollbackToken()
          val builder = new ListBuffer[A]
          var i = 0
          while ({
            if (i >= maxArity) maxArityError(cursor)
            cursor.push(i)
            builder += cursor.decode(a, in)
            cursor.pop()
            i += 1
            in.isNextToken(',')
          }) ()
          if (in.isCurrentToken(']')) builder.result()
          else in.arrayEndOrCommaError()
        }
      } else in.decodeError("Expected JSON array")

    def encodeValue(xs: List[A], out: JsonWriter): Unit = {
      out.writeArrayStart()
      var list = xs
      while (list ne Nil) {
        a.encodeValue(list.head, out)
        list = list.tail
      }
      out.writeArrayEnd()
    }

    def decodeKey(in: JsonReader): List[A] =
      in.decodeError("Cannot use vectors as keys")

    def encodeKey(xs: List[A], out: JsonWriter): Unit =
      out.encodeError("Cannot use vectors as keys")

    private[this] def maxArityError(cursor: Cursor): Nothing =
      throw cursor.payloadError(
        this,
        s"Input $expecting exceeded max arity of $maxArity"
      )
  }

  private def vector[A](
      member: Schema[A]
  ): JCodec[Vector[A]] = new JCodec[Vector[A]] {
    private[this] val a = apply(member)

    def expecting: String = "list"

    override def canBeKey: Boolean = false

    def decodeValue(cursor: Cursor, in: JsonReader): Vector[A] =
      if (in.isNextToken('[')) {
        if (in.isNextToken(']')) Vector.empty
        else {
          in.rollbackToken()
          val builder = Vector.newBuilder[A]
          var i = 0
          while ({
            if (i >= maxArity) maxArityError(cursor)
            cursor.push(i)
            builder += cursor.decode(a, in)
            cursor.pop()
            i += 1
            in.isNextToken(',')
          }) ()
          if (in.isCurrentToken(']')) builder.result()
          else in.arrayEndOrCommaError()
        }
      } else in.decodeError("Expected JSON array")

    def encodeValue(xs: Vector[A], out: JsonWriter): Unit = {
      out.writeArrayStart()
      xs.foreach(x => a.encodeValue(x, out))
      out.writeArrayEnd()
    }

    def decodeKey(in: JsonReader): Vector[A] =
      in.decodeError("Cannot use vectors as keys")

    def encodeKey(xs: Vector[A], out: JsonWriter): Unit =
      out.encodeError("Cannot use vectors as keys")

    private[this] def maxArityError(cursor: Cursor): Nothing =
      throw cursor.payloadError(
        this,
        s"Input $expecting exceeded max arity of $maxArity"
      )
  }

  private def indexedSeq[A](
      member: Schema[A]
  ): JCodec[IndexedSeq[A]] = new JCodec[IndexedSeq[A]] {
    private[this] val a = apply(member)
    def expecting: String = "list"

    override def canBeKey: Boolean = false

    val withBuilder = CollectionTag.IndexedSeqTag.compactBuilder(member)

    def decodeValue(cursor: Cursor, in: JsonReader): IndexedSeq[A] =
      if (in.isNextToken('[')) {
        if (in.isNextToken(']')) Vector.empty
        else {
          in.rollbackToken()
          withBuilder { put =>
            var i = 0
            while ({
              if (i >= maxArity) maxArityError(cursor)
              cursor.push(i)
              put(cursor.decode(a, in))
              cursor.pop()
              i += 1
              in.isNextToken(',')
            }) ()
            if (!in.isCurrentToken(']')) {
              in.arrayEndOrCommaError()
            }
          }
        }
      } else in.decodeError("Expected JSON array")

    def encodeValue(xs: IndexedSeq[A], out: JsonWriter): Unit = {
      out.writeArrayStart()
      xs match {
        case x: ArraySeq[A] =>
          val xs = x.unsafeArray.asInstanceOf[Array[A]]
          var i = 0
          while (i < xs.length) {
            a.encodeValue(xs(i), out)
            i += 1
          }
        case _ =>
          xs.foreach(x => a.encodeValue(x, out))
      }
      out.writeArrayEnd()
    }

    def decodeKey(in: JsonReader): IndexedSeq[A] =
      in.decodeError("Cannot use vectors as keys")

    def encodeKey(xs: IndexedSeq[A], out: JsonWriter): Unit =
      out.encodeError("Cannot use vectors as keys")

    private[this] def maxArityError(cursor: Cursor): Nothing =
      throw cursor.payloadError(
        this,
        s"Input $expecting exceeded max arity of $maxArity"
      )
  }

  private def set[A](
      member: Schema[A]
  ): JCodec[Set[A]] = new JCodec[Set[A]] {
    private[this] val a = apply(member)
    def expecting: String = "list"

    override def canBeKey: Boolean = false

    def decodeValue(cursor: Cursor, in: JsonReader): Set[A] =
      if (in.isNextToken('[')) {
        if (in.isNextToken(']')) Set.empty
        else {
          in.rollbackToken()
          val builder = Set.newBuilder[A]
          var i = 0
          while ({
            if (i >= maxArity) maxArityError(cursor)
            cursor.push(i)
            builder += cursor.decode(a, in)
            cursor.pop()
            i += 1
            in.isNextToken(',')
          }) ()
          if (in.isCurrentToken(']')) builder.result()
          else in.arrayEndOrCommaError()
        }
      } else in.decodeError("Expected JSON array")

    def encodeValue(xs: Set[A], out: JsonWriter): Unit = {
      out.writeArrayStart()
      xs.foreach(x => a.encodeValue(x, out))
      out.writeArrayEnd()
    }

    def decodeKey(in: JsonReader): Set[A] =
      in.decodeError("Cannot use vectors as keys")

    def encodeKey(xs: Set[A], out: JsonWriter): Unit =
      out.encodeError("Cannot use vectors as keys")

    private[this] def maxArityError(cursor: Cursor): Nothing =
      throw cursor.payloadError(
        this,
        s"Input $expecting exceeded max arity of $maxArity"
      )
  }

  private def objectMap[K, V](
      jk: JCodec[K],
      jv: JCodec[V]
  ): JCodec[Map[K, V]] = new JCodec[Map[K, V]] {
    val expecting: String = "map"

    override def canBeKey: Boolean = false

    def decodeValue(cursor: Cursor, in: JsonReader): Map[K, V] =
      if (in.isNextToken('{')) {
        if (in.isNextToken('}')) Map.empty
        else {
          in.rollbackToken()
          val builder =
            if (preserveMapOrder) ListMap.newBuilder[K, V]
            else Map.newBuilder[K, V]
          var i = 0
          while ({
            if (i >= maxArity) maxArityError(cursor)
            builder += (
              (
                jk.decodeKey(in), {
                  cursor.push(i)
                  val result = cursor.decode(jv, in)
                  cursor.pop()
                  result
                }
              )
            )
            i += 1
            in.isNextToken(',')
          }) ()
          if (in.isCurrentToken('}')) builder.result()
          else in.objectEndOrCommaError()
        }
      } else in.decodeError("Expected JSON object")

    def encodeValue(xs: Map[K, V], out: JsonWriter): Unit = {
      out.writeObjectStart()
      xs.foreach { kv =>
        jk.encodeKey(kv._1, out)
        jv.encodeValue(kv._2, out)
      }
      out.writeObjectEnd()
    }

    def decodeKey(in: JsonReader): Map[K, V] =
      in.decodeError("Cannot use maps as keys")

    def encodeKey(xs: Map[K, V], out: JsonWriter): Unit =
      out.encodeError("Cannot use maps as keys")

    private[this] def maxArityError(cursor: Cursor): Nothing =
      throw cursor.payloadError(
        this,
        s"Input $expecting exceeded max arity of $maxArity"
      )
  }

  private def arrayMap[K, V](
      k: Schema[K],
      v: Schema[V]
  ): JCodec[Map[K, V]] = {
    val kField = Field.required[(K, V), K]("key", k, _._1)
    val vField = Field.required[(K, V), V]("value", v, _._2)
    val kvCodec = Schema.struct(Vector(kField, vField))(fields =>
      (fields(0).asInstanceOf[K], fields(1).asInstanceOf[V])
    )
    listImpl(kvCodec).biject(_.toMap, _.toList)
  }

  private def flexibleNullParsingMap[K, V](
      jk: JCodec[K],
      jv: JCodec[V]
  ): JCodec[Map[K, V]] =
    new JCodec[Map[K, V]] {
      val expecting: String = "map"

      override def canBeKey: Boolean = false

      def decodeValue(cursor: Cursor, in: JsonReader): Map[K, V] =
        if (in.isNextToken('{')) {
          if (in.isNextToken('}')) Map.empty
          else {
            in.rollbackToken()
            val builder = Map.newBuilder[K, V]
            var i = 0
            while ({
              if (i >= maxArity) maxArityError(cursor)
              val key = jk.decodeKey(in)
              cursor.push(i)
              if (in.isNextToken('n')) {
                in.readNullOrError[Unit]((), "Expected null")
              } else {
                in.rollbackToken()
                val value = cursor.decode(jv, in)
                builder += (key -> value)
              }
              cursor.pop()

              i += 1
              in.isNextToken(',')
            }) ()
            if (in.isCurrentToken('}')) builder.result()
            else in.objectEndOrCommaError()
          }
        } else in.decodeError("Expected JSON object")

      def encodeValue(xs: Map[K, V], out: JsonWriter): Unit = {
        out.writeObjectStart()
        xs.foreach { kv =>
          jk.encodeKey(kv._1, out)
          jv.encodeValue(kv._2, out)
        }
        out.writeObjectEnd()
      }

      def decodeKey(in: JsonReader): Map[K, V] =
        in.decodeError("Cannot use maps as keys")

      def encodeKey(xs: Map[K, V], out: JsonWriter): Unit =
        out.encodeError("Cannot use maps as keys")

      private def maxArityError(cursor: Cursor): Nothing =
        throw cursor.payloadError(
          this,
          s"Input $expecting exceeded max arity of $maxArity"
        )
    }

  override def collection[C[_], A](
      shapeId: ShapeId,
      hints: Hints,
      tag: CollectionTag[C],
      member: Schema[A]
  ): JCodec[C[A]] = {
    tag match {
      case CollectionTag.ListTag       => listImpl(member)
      case CollectionTag.SetTag        => set(member)
      case CollectionTag.VectorTag     => vector(member)
      case CollectionTag.IndexedSeqTag => indexedSeq(member)
    }
  }

  override def map[K, V](
      shapeId: ShapeId,
      hints: Hints,
      key: Schema[K],
      value: Schema[V]
  ): JCodec[Map[K, V]] = {
    val jk = apply(key)
    val jv = apply(value)
    if (jk.canBeKey) {
      if (flexibleCollectionsSupport && !value.isOption)
        flexibleNullParsingMap(jk, jv)
      else objectMap(jk, jv)
    } else arrayMap(key, value)
  }

  override def biject[A, B](
      schema: Schema[A],
      bijection: Bijection[A, B]
  ): JCodec[B] =
    apply(schema).biject(bijection, bijection.from)

  override def refine[A, B](
      schema: Schema[A],
      refinement: Refinement[A, B]
  ): JCodec[B] =
    apply(schema).biject(refinement.asThrowingFunction, refinement.from)

  override def lazily[A](suspend: Lazy[Schema[A]]): JCodec[A] = new JCodec[A] {
    lazy val underlying = apply(suspend.value)

    def expecting: String = underlying.expecting

    def decodeValue(cursor: Cursor, in: JsonReader): A =
      underlying.decodeValue(cursor, in)

    def encodeValue(x: A, out: JsonWriter): Unit =
      underlying.encodeValue(x, out)

    def decodeKey(in: JsonReader): A = underlying.decodeKey(in)

    def encodeKey(x: A, out: JsonWriter): Unit = underlying.encodeKey(x, out)
  }

  private type Writer[A] = A => JsonWriter => Unit

  private def taggedUnion[U](
      alternatives: Vector[Alt[U, _]]
  )(dispatch: Alt.Dispatcher[U]): JCodec[U] =
    new JCodec[U] {
      val expecting: String = "tagged-union"

      override def canBeKey: Boolean = false

      def jsonLabel[A](alt: Alt[U, A]): String =
        alt.hints.get(JsonName) match {
          case None    => alt.label
          case Some(x) => x.value
        }

      private[this] val handlerMap =
        new util.HashMap[String, (Cursor, JsonReader) => U] {
          def handler[A](alt: Alt[U, A]) = {
            val codec = apply(alt.schema)
            (cursor: Cursor, reader: JsonReader) =>
              alt.inject(cursor.decode(codec, reader))
          }

          alternatives.foreach(alt => put(jsonLabel(alt), handler(alt)))
        }

      def decodeValue(cursor: Cursor, in: JsonReader): U =
        if (in.isNextToken('{')) {
          if (in.isNextToken('}'))
            in.decodeError("Expected a single key/value pair")
          else {
            in.rollbackToken()
            val key = in.readKeyAsString()
            cursor.push(key)
            val handler = handlerMap.get(key)
            if (handler eq null) in.discriminatorValueError(key)
            val result = handler(cursor, in)
            cursor.pop()
            if (in.isNextToken('}')) result
            else {
              in.rollbackToken()
              in.decodeError(s"Expected no other field after $key")
            }
          }
        } else in.decodeError("Expected JSON object")

      val precompiler = new smithy4s.schema.Alt.Precompiler[Writer] {
        def apply[A](label: String, instance: Schema[A]): Writer[A] = {
          val jsonLabel =
            instance.hints.get(JsonName).map(_.value).getOrElse(label)
          val jcodecA = instance.compile(self)
          a =>
            out => {
              out.writeObjectStart()
              out.writeKey(jsonLabel)
              jcodecA.encodeValue(a, out)
              out.writeObjectEnd()
            }
        }
      }
      val writer = dispatch.compile(precompiler)

      def encodeValue(u: U, out: JsonWriter): Unit = {
        writer(u)(out)
      }

      def decodeKey(in: JsonReader): U =
        in.decodeError("Cannot use coproducts as keys")

      def encodeKey(u: U, out: JsonWriter): Unit =
        out.encodeError("Cannot use coproducts as keys")
    }

  private def lenientTaggedUnion[U](
      alternatives: Vector[Alt[U, _]]
  )(dispatch: Alt.Dispatcher[U]): JCodec[U] =
    new JCodec[U] {
      val expecting: String = "tagged-union"

      override def canBeKey: Boolean = false

      def jsonLabel[A](alt: Alt[U, A]): String =
        alt.hints.get(JsonName) match {
          case None    => alt.label
          case Some(x) => x.value
        }

      private[this] val handlerMap =
        new util.HashMap[String, (Cursor, JsonReader) => U] {
          def handler[A](alt: Alt[U, A]) = {
            val codec = apply(alt.schema)
            (cursor: Cursor, reader: JsonReader) =>
              alt.inject(cursor.decode(codec, reader))
          }

          alternatives.foreach(alt => put(jsonLabel(alt), handler(alt)))
        }

      def decodeValue(cursor: Cursor, in: JsonReader): U = {
        var result: U = null.asInstanceOf[U]
        if (in.isNextToken('{')) {
          if (!in.isNextToken('}')) {
            in.rollbackToken()
            while ({
              val key = in.readKeyAsString()
              val handler = handlerMap.get(key)
              if (handler eq null) in.skip()
              else if (in.isNextToken('n')) {
                in.readNullOrError((), "expected null")
              } else {
                in.rollbackToken()
                if (result != null) {
                  in.decodeError("Expected a single non-null value")
                } else {
                  result = handler(cursor, in)
                }
              }
              in.isNextToken(',')
            }) ()
            if (!in.isCurrentToken('}')) in.objectEndOrCommaError()
          }
          if (result != null) {
            result
          } else {
            in.decodeError("Expected a single non-null value")
          }
        } else in.decodeError("Expected JSON object")
      }
      val precompiler = new smithy4s.schema.Alt.Precompiler[Writer] {
        def apply[A](label: String, instance: Schema[A]): Writer[A] = {
          val jsonLabel =
            instance.hints.get(JsonName).map(_.value).getOrElse(label)
          val jcodecA = instance.compile(self)
          a =>
            out => {
              out.writeObjectStart()
              out.writeKey(jsonLabel)
              jcodecA.encodeValue(a, out)
              out.writeObjectEnd()
            }
        }
      }
      val writer = dispatch.compile(precompiler)

      def encodeValue(u: U, out: JsonWriter): Unit = {
        writer(u)(out)
      }

      def decodeKey(in: JsonReader): U =
        in.decodeError("Cannot use coproducts as keys")

      def encodeKey(u: U, out: JsonWriter): Unit =
        out.encodeError("Cannot use coproducts as keys")
    }

  private def untaggedUnion[U](
      alternatives: Vector[Alt[U, _]]
  )(dispatch: Alt.Dispatcher[U]): JCodec[U] = new JCodec[U] {
    def expecting: String = "untaggedUnion"

    override def canBeKey: Boolean = false

    private[this] val handlerList: Array[(Cursor, JsonReader) => U] = {
      val res = Array.newBuilder[(Cursor, JsonReader) => U]

      def handler[A](alt: Alt[U, A]) = {
        val codec = apply(alt.schema)
        (cursor: Cursor, reader: JsonReader) =>
          alt.inject(cursor.decode(codec, reader))
      }

      alternatives.foreach(alt => res += handler(alt))
      res.result()
    }

    def decodeValue(cursor: Cursor, in: JsonReader): U = {
      var z: U = null.asInstanceOf[U]
      val len = handlerList.length
      var i = 0
      while (z == null && i < len) {
        in.setMark()
        val handler = handlerList(i)
        try {
          z = handler(cursor, in)
        } catch {
          case _: Throwable =>
            in.rollbackToMark()
            i += 1
        }
      }
      if (z != null) z
      else cursor.payloadError(this, "Could not decode untagged union")
    }

    val precompiler = new smithy4s.schema.Alt.Precompiler[Writer] {
      def apply[A](label: String, instance: Schema[A]): Writer[A] = {
        val jcodecA = instance.compile(self)
        a => out => jcodecA.encodeValue(a, out)
      }
    }
    val writer = dispatch.compile(precompiler)

    def encodeValue(u: U, out: JsonWriter): Unit = {
      writer(u)(out)
    }

    def decodeKey(in: JsonReader): U =
      in.decodeError("Cannot use coproducts as keys")

    def encodeKey(u: U, out: JsonWriter): Unit =
      out.encodeError("Cannot use coproducts as keys")
  }

  private def discriminatedUnion[U](
      alternatives: Vector[Alt[U, _]],
      discriminated: Discriminated
  )(dispatch: Alt.Dispatcher[U]): JCodec[U] =
    new JCodec[U] {
      def expecting: String = "discriminated-union"

      override def canBeKey: Boolean = false

      def jsonLabel[A](alt: Alt[U, A]): String =
        alt.hints.get(JsonName) match {
          case None    => alt.label
          case Some(x) => x.value
        }

      private[this] val handlerMap =
        new util.HashMap[String, (Cursor, JsonReader) => U] {
          def handler[A](
              alt: Alt[U, A]
          ): (Cursor, JsonReader) => U = {
            val codec = apply(alt.schema)
            (cursor: Cursor, reader: JsonReader) =>
              alt.inject(cursor.decode(codec, reader))
          }

          alternatives.foreach(alt => put(jsonLabel(alt), handler(alt)))
        }

      def decodeValue(cursor: Cursor, in: JsonReader): U =
        if (in.isNextToken('{')) {
          in.setMark()
          if (in.skipToKey(discriminated.value)) {
            val key = in.readString("")
            in.rollbackToMark()
            in.rollbackToken()
            cursor.push(key)
            val handler = handlerMap.get(key)
            if (handler eq null) in.discriminatorValueError(key)
            val result = handler(cursor, in)
            cursor.pop()
            result
          } else
            in.decodeError(
              s"Unable to find discriminator ${discriminated.value}"
            )
        } else in.decodeError("Expected JSON object")

      val precompiler = new smithy4s.schema.Alt.Precompiler[Writer] {
        def apply[A](label: String, instance: Schema[A]): Writer[A] = {
          val jsonLabel =
            instance.hints.get(JsonName).map(_.value).getOrElse(label)
          val jcodecA = instance
            .addHints(
              Hints(DiscriminatedUnionMember(discriminated.value, jsonLabel))
            )
            .compile(self)
          a => out => jcodecA.encodeValue(a, out)
        }
      }
      val writer = dispatch.compile(precompiler)

      def encodeValue(u: U, out: JsonWriter): Unit = {
        writer(u)(out)
      }

      def decodeKey(in: JsonReader): U =
        in.decodeError("Cannot use coproducts as keys")

      def encodeKey(x: U, out: JsonWriter): Unit =
        out.encodeError("Cannot use coproducts as keys")
    }

  override def union[U](
      shapeId: ShapeId,
      hints: Hints,
      alternatives: Vector[Alt[U, _]],
      dispatch: Alt.Dispatcher[U]
  ): JCodec[U] = hints match {
    case Untagged.hint(_)      => untaggedUnion(alternatives)(dispatch)
    case Discriminated.hint(d) => discriminatedUnion(alternatives, d)(dispatch)
    case _ =>
      if (lenientTaggedUnionDecoding) lenientTaggedUnion(alternatives)(dispatch)
      else taggedUnion(alternatives)(dispatch)
  }

  override def enumeration[E](
      shapeId: ShapeId,
      hints: Hints,
      tag: EnumTag[E],
      values: List[EnumValue[E]],
      total: E => EnumValue[E]
  ): JCodec[E] =
    tag match {
      case EnumTag.IntEnum() =>
        handleIntEnum(shapeId, hints, values, total, tag)
      case _ =>
        handleEnum(shapeId, hints, values, total, tag)
    }

  private def handleEnum[E](
      shapeId: ShapeId,
      hints: Hints,
      values: List[EnumValue[E]],
      total: E => EnumValue[E],
      tag: EnumTag[E]
  ): JCodec[E] = new JCodec[E] {
    private val nameMap: Map[String, E] =
      values.map(v => v.stringValue -> v.value).toMap

    private def fromName(v: String): Option[E] =
      nameMap.get(v)

    private def fromNameOpen(v: String, unknown: String => E): E =
      nameMap.getOrElse(v, unknown(v))

    val expecting: String =
      s"enumeration: [${values.map(_.stringValue).mkString(", ")}]"

    private val decode: (JsonReader, String) => E = tag match {
      case EnumTag.OpenStringEnum(unknown) =>
        (_, str) => fromNameOpen(str, unknown)
      case _ =>
        (in, str) =>
          fromName(str) match {
            case Some(value) => value
            case None        => in.enumValueError(str)
          }
    }

    def decodeValue(cursor: Cursor, in: JsonReader): E = {
      val str = in.readString(null)
      decode(in, str)
    }

    def encodeValue(x: E, out: JsonWriter): Unit =
      out.writeVal(total(x).stringValue)

    def decodeKey(in: JsonReader): E = {
      val str = in.readKeyAsString()
      decode(in, str)
    }

    def encodeKey(x: E, out: JsonWriter): Unit =
      out.writeKey(total(x).stringValue)
  }

  private def handleIntEnum[E](
      shapeId: ShapeId,
      hints: Hints,
      values: List[EnumValue[E]],
      total: E => EnumValue[E],
      tag: EnumTag[E]
  ): JCodec[E] = new JCodec[E] {
    private val ordinalMap: Map[Int, E] =
      values.map(v => v.intValue -> v.value).toMap

    private def fromOrdinal(v: Int): Option[E] =
      ordinalMap.get(v)

    private def fromOrdinalOpen(v: Int, unknown: Int => E): E =
      ordinalMap.getOrElse(v, unknown(v))

    val expecting: String =
      s"enumeration: [${values.map(_.stringValue).mkString(", ")}]"

    private val decode: (JsonReader, Int) => E = tag match {
      case EnumTag.OpenIntEnum(unknown) =>
        (_, i) => fromOrdinalOpen(i, unknown)
      case _ =>
        (in, i) =>
          fromOrdinal(i) match {
            case Some(value) => value
            case None        => in.enumValueError(i)
          }
    }

    def decodeValue(cursor: Cursor, in: JsonReader): E = {
      val i = in.readInt()
      decode(in, i)
    }

    def encodeValue(x: E, out: JsonWriter): Unit =
      out.writeVal(total(x).intValue)

    def decodeKey(in: JsonReader): E = {
      val i = in.readKeyAsInt()
      decode(in, i)
    }

    def encodeKey(x: E, out: JsonWriter): Unit =
      out.writeKey(total(x).intValue)
  }

  override def option[A](schema: Schema[A]): JCodec[Option[A]] =
    new JCodec[Option[A]] {
      val underlying: JCodec[A] = self(schema)
      val aIsNullable =
        schema.hints.has(Nullable) && schema.isOption
      def expecting: String = s"JsNull or ${underlying.expecting}"
      def decodeKey(in: JsonReader): Option[A] = ???
      def encodeKey(x: Option[A], out: JsonWriter): Unit = ???
      def encodeValue(x: Option[A], out: JsonWriter): Unit = x match {
        case None        => out.writeNull()
        case Some(value) => underlying.encodeValue(value, out)
      }

      def decodeValue(cursor: Cursor, in: JsonReader): Option[A] =
        // if `A` is an option and has nullable, we delegate the handling of `null` to it.
        // This allows for supporting Json-merge patches, where the absence of value
        // and the presence of "null" have different meanings.
        if (in.isNextToken('n') && !aIsNullable)
          in.readNullOrError[Option[A]](None, "Expected null")
        else {
          in.rollbackToken()
          Some(underlying.decodeValue(cursor, in))
        }
    }

  private def jsonLabel[A, Z](field: Field[Z, A]): String =
    field.hints.get(JsonName) match {
      case None    => field.label
      case Some(x) => x.value
    }

  private type Handler = (Cursor, JsonReader, util.HashMap[String, Any]) => Unit

  private def fieldHandler[Z, A](
      field: Field[Z, A]
  ): Handler = {
    val codec = apply(field.schema)
    val label = field.label
    (cursor, in, mmap) =>
      val _ = mmap.put(
        label, {
          cursor.push(label)
          val result = cursor.decode(codec, in)
          cursor.pop()
          result
        }
      )
  }

  private def fieldEncoder[Z, A](
      field: Field[Z, A]
  ): (Z, JsonWriter) => Unit = {
    val codec = apply(field.schema)
    val jLabel = jsonLabel(field)
    val writeLabel: JsonWriter => Unit =
      if (jLabel.forall(JsonWriter.isNonEscapedAscii)) {
        _.writeNonEscapedAsciiKey(jLabel)
      } else _.writeKey(jLabel)

    if (explicitDefaultsEncoding) { (z: Z, out: JsonWriter) =>
      writeLabel(out)
      codec.encodeValue(field.get(z), out)
    } else { (z: Z, out: JsonWriter) =>
      field.foreachUnlessDefault(z) { (a: A) =>
        writeLabel(out)
        codec.encodeValue(a, out)
      }
    }
  }

  private type Fields[Z] = Vector[Field[Z, _]]
  private type LabelledFields[Z] = Vector[(Field[Z, _], String, Any)]
  private def labelledFields[Z](fields: Fields[Z]): LabelledFields[Z] =
    fields.map { field =>
      val jLabel = jsonLabel(field)
      val decoded: Option[Any] = field.schema.getDefaultValue
      val default = decoded.orNull
      (field, jLabel, default)
    }

  private def nonPayloadStruct[Z](
      fields: LabelledFields[Z],
      structHints: Hints
  )(
      const: Vector[Any] => Z,
      encode: (Z, JsonWriter, Vector[(Z, JsonWriter) => Unit]) => Unit
  ): JCodec[Z] =
    new JCodec[Z] {

      private[this] val handlers =
        new util.HashMap[String, Handler](fields.length << 1, 0.5f) {
          fields.foreach { case (field, jLabel, _) =>
            put(jLabel, fieldHandler(field))
          }
        }

      private[this] val documentEncoders =
        fields.map(labelledField => fieldEncoder(labelledField._1))

      def expecting: String = "object"

      override def canBeKey = false

      def decodeValue(cursor: Cursor, in: JsonReader): Z =
        decodeValue_(cursor, in)(emptyMetadata)

      private def decodeValue_(
          cursor: Cursor,
          in: JsonReader
      ): scala.collection.Map[String, Any] => Z = {
        val buffer = new util.HashMap[String, Any](handlers.size << 1, 0.5f)
        if (in.isNextToken('{')) {
          if (!in.isNextToken('}')) {
            in.rollbackToken()
            while ({
              val handler = handlers.get(in.readKeyAsString())
              if (handler eq null) in.skip()
              else handler(cursor, in, buffer)
              in.isNextToken(',')
            }) ()
            if (!in.isCurrentToken('}')) in.objectEndOrCommaError()
          }
        } else in.decodeError("Expected JSON object")

        // At this point, we have parsed the json and retrieved
        // all the values that interest us for the construction
        // of our domain object.
        // We re-order the values following the order of the schema
        // fields before calling the constructor.
        { (meta: scala.collection.Map[String, Any]) =>
          meta.foreach(kv => buffer.put(kv._1, kv._2))
          val stage2 = new VectorBuilder[Any]
          fields.foreach { case (f, jsonLabel, default) =>
            stage2 += {
              val value = buffer.get(f.label)
              if (value == null) {
                if (default == null)
                  cursor.requiredFieldError(jsonLabel, jsonLabel)
                else default
              } else value
            }
          }
          const(stage2.result())
        }
      }

      def encodeValue(z: Z, out: JsonWriter): Unit =
        encode(z, out, documentEncoders)

      def decodeKey(in: JsonReader): Z =
        in.decodeError("Cannot use products as keys")

      def encodeKey(x: Z, out: JsonWriter): Unit =
        out.encodeError("Cannot use products as keys")
    }

  private def basicStruct[A, S](
      fields: LabelledFields[S],
      structHints: Hints
  )(make: Vector[Any] => S): JCodec[S] = {
    val encode = {
      (
          z: S,
          out: JsonWriter,
          documentEncoders: Vector[(S, JsonWriter) => Unit]
      ) =>
        out.writeObjectStart()
        documentEncoders.foreach(encoder => encoder(z, out))
        out.writeObjectEnd()
    }

    nonPayloadStruct(fields, structHints)(make, encode)
  }

  override def struct[S](
      shapeId: ShapeId,
      hints: Hints,
      fields: Vector[Field[S, _]],
      make: IndexedSeq[Any] => S
  ): JCodec[S] = {
    val lFields = labelledFields[S](fields)
    hints match {
      case DiscriminatedUnionMember.hint(d) =>
        val encode =
          if (
            d.propertyName.forall(JsonWriter.isNonEscapedAscii) &&
            d.alternativeLabel.forall(JsonWriter.isNonEscapedAscii)
          ) {
            (
                z: S,
                out: JsonWriter,
                documentEncoders: Vector[(S, JsonWriter) => Unit]
            ) =>
              out.writeObjectStart()
              out.writeNonEscapedAsciiKey(d.propertyName)
              out.writeNonEscapedAsciiVal(d.alternativeLabel)
              documentEncoders.foreach(encoder => encoder(z, out))
              out.writeObjectEnd()
          } else {
            (
                z: S,
                out: JsonWriter,
                documentEncoders: Vector[(S, JsonWriter) => Unit]
            ) =>
              out.writeObjectStart()
              out.writeKey(d.propertyName)
              out.writeVal(d.alternativeLabel)
              documentEncoders.foreach(encoder => encoder(z, out))
              out.writeObjectEnd()
          }
        nonPayloadStruct(lFields, hints)(make, encode)
      case _ =>
        basicStruct(lFields, hints)(make)
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy