diff --git a/json/pickler/src/main/scala/sttp/tapir/json/pickler/CreateDerivedEnumerationPickler.scala b/json/pickler/src/main/scala/sttp/tapir/json/pickler/CreateDerivedEnumerationPickler.scala index dd015f25ba..d1302c64aa 100644 --- a/json/pickler/src/main/scala/sttp/tapir/json/pickler/CreateDerivedEnumerationPickler.scala +++ b/json/pickler/src/main/scala/sttp/tapir/json/pickler/CreateDerivedEnumerationPickler.scala @@ -39,14 +39,14 @@ class CreateDerivedEnumerationPickler[T: ClassTag]( val tapirPickle = new TapirPickle[T] { override lazy val reader: Reader[T] = { val readersForPossibleValues: Seq[TaggedReader[T]] = - validator.possibleValues.zip(childReadWriters.map(_._1)).map { case (enumValue, reader) => - TaggedReader.Leaf[T](encode(enumValue).toString, reader.asInstanceOf[LeafWrapper[_]].r.asInstanceOf[Reader[T]]) + childReadWriters.map { case (enumValue, reader, _) => + TaggedReader.Leaf[T](encode(enumValue.asInstanceOf[T]).toString, reader.asInstanceOf[LeafWrapper[_]].r.asInstanceOf[Reader[T]]) } new TaggedReader.Node[T](readersForPossibleValues: _*) } override lazy val writer: Writer[T] = - new TaggedWriter.Node[T](childReadWriters.map(_._2.asInstanceOf[TaggedWriter[T]]): _*) { + new TaggedWriter.Node[T](childReadWriters.map(_._3.asInstanceOf[TaggedWriter[T]]): _*) { override def findWriterWithKey(v: Any): (String, String, ObjectWriter[T]) = val (tagKey, tagValue, writer) = super.findWriterWithKey(v) // Here our custom encoding transforms the value of a singleton object @@ -57,15 +57,17 @@ class CreateDerivedEnumerationPickler[T: ClassTag]( new Pickler[T](tapirPickle, schema) } - private inline def buildEnumerationReadWriters[T: ClassTag, Cases <: Tuple]: List[(Types#Reader[_], Types#Writer[_])] = + private inline def buildEnumerationReadWriters[T: ClassTag, Cases <: Tuple]: List[(Any, Types#Reader[_], Types#Writer[_])] = inline erasedValue[Cases] match { case _: (enumerationCase *: enumerationCasesTail) => - val processedHead = readWriterForEnumerationCase[enumerationCase] + val (reader, writer) = readWriterForEnumerationCase[enumerationCase] val processedTail = buildEnumerationReadWriters[T, enumerationCasesTail] - (processedHead +: processedTail) + ((productValue[enumerationCase], reader, writer) +: processedTail) case _: EmptyTuple.type => Nil } + private inline def productValue[E] = summonFrom { case m: Mirror.ProductOf[E] => m.fromProduct(EmptyTuple) } + /** Enumeration cases and case objects in an enumeration need special writers and readers, which are generated here, instead of being * taken from child picklers. For example, for enum Color and case values Red and Blue, a Writer should just use the object Red or Blue * and serialize it to "Red" or "Blue". If user needs to encode the singleton object using a custom function, this happens on a higher diff --git a/json/pickler/src/test/scala/sttp/tapir/json/pickler/Fixtures.scala b/json/pickler/src/test/scala/sttp/tapir/json/pickler/Fixtures.scala index 5878fe2622..167de23212 100644 --- a/json/pickler/src/test/scala/sttp/tapir/json/pickler/Fixtures.scala +++ b/json/pickler/src/test/scala/sttp/tapir/json/pickler/Fixtures.scala @@ -90,3 +90,7 @@ object Fixtures: sealed trait NotAllSealedVariant case object NotAllSealedVariantA extends NotAllSealedVariant case class NotAllSealedVariantB(innerField: Int) extends NotAllSealedVariant + + enum NotAlphabetical: + case Xyz + case Fgh diff --git a/json/pickler/src/test/scala/sttp/tapir/json/pickler/PicklerEnumTest.scala b/json/pickler/src/test/scala/sttp/tapir/json/pickler/PicklerEnumTest.scala index 0ebf26ce5d..ac3ef40c17 100644 --- a/json/pickler/src/test/scala/sttp/tapir/json/pickler/PicklerEnumTest.scala +++ b/json/pickler/src/test/scala/sttp/tapir/json/pickler/PicklerEnumTest.scala @@ -107,4 +107,18 @@ class PicklerEnumTest extends AnyFlatSpec with Matchers { 18 -> picklerMagenta )""") } + + it should "encode and decode an enum where the cases are not alphabetically sorted" in { + // given + import generic.auto.* // for Pickler auto-derivation + + // when + val testPickler = Pickler.derived[NotAlphabetical] + val codec = testPickler.toCodec + val encoded = codec.encode(NotAlphabetical.Xyz) + + // then + encoded shouldBe """"Xyz"""" + codec.decode(encoded) shouldBe Value(NotAlphabetical.Xyz) + } }