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

Support Scala 3 Enums #1017

Open
nemoo opened this issue Apr 24, 2024 · 3 comments
Open

Support Scala 3 Enums #1017

nemoo opened this issue Apr 24, 2024 · 3 comments

Comments

@nemoo
Copy link

nemoo commented Apr 24, 2024

Let's be honest, this is a feature request, not a bug report.

Play-json currently supports Enumerations out of the box via methods like Json.formatEnum(X)
https://github.com/playframework/play-json/blob/main/play-json/shared/src/main/scala/play/api/libs/json/Reads.scala#L437

It would be great if the current Scala 3 way of doing enums: https://docs.scala-lang.org/scala3/reference/enums/enums.html would also be supported in a similar fashion.

@vasilmkd
Copy link

Hi there. We had a user report in the Scala plugin for IntelliJ Discord server about the compiler seemingly deadlocking trying to derive an instance of Json.reads for a Scala 3 enum.

Is it officially documented somewhere that this is not supported? I'm trying to decide whether a ticket needs to be opened with the Scala 3 team.

Thank you.

@nemoo
Copy link
Author

nemoo commented Apr 29, 2024

@vasilmkd I had the same problem with the compiler crash. I created a separate issue for that here: #1019

@gbarmashiahflir
Copy link

I also Encountered the compiler crash.
Anyway, here's code that will serial scala 3 enums to json string and back:

import play.api.libs.json.Format

trait EnumFormat[T] extends Format[T]

object EnumFormat:
  inline def derived[T]: EnumFormat[T] = ${ EnumMacros.enumFormatMacro[T] }
import play.api.libs.json.*
import scala.quoted.*

object EnumMacros {

  def enumFormatMacro[T: Type](using Quotes): Expr[EnumFormat[T]] =
    import quotes.reflect.*

    val tpe = TypeRepr.of[T]

    val sym = tpe.classSymbol match
      case Some(sym) if sym.flags.is(Flags.Enum) && !sym.flags.is(Flags.JavaDefined) =>
        sym
      case _ =>
        report.errorAndAbort(s"${tpe.show} is not an enum type")

    def reifyValueOf(name: Expr[String]) =
      Select
        .overloaded(Ref(sym.companionModule), "valueOf", Nil, name.asTerm :: Nil)
        .asExprOf[T & reflect.Enum]

    '{
      new EnumFormat[T] {
        private def valueOfUnsafe(name: String): T = ${ reifyValueOf('name) }

        override def reads(json: JsValue): JsResult[T] = try {
          JsSuccess(valueOfUnsafe(json.as[JsString].value))
        } catch {
          case e: NoSuchElementException => JsError(e.getMessage)
        }

        override def writes(o: T): JsValue = JsString(o.toString)
      }
    }
}

And then the usage is as simple as:

enum MyEnum derives EnumFormat {
  case MyEnumA, MyEnumB, MyEnumC, MyEnumD
}

and:
Json.toJson(MyEnum.MyEnumA)
or:
JsString("MyEnumB").as[MyEnum]

no crashes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants