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

Compile error with PlayJson macro and cats.data.Newtype (via NonEmptySet) #2582

Closed
ronanM opened this issue Oct 23, 2018 · 6 comments
Closed

Comments

@ronanM
Copy link

ronanM commented Oct 23, 2018

A minimal failing snippet:

build.sbt:

scalaVersion := "2.12.7"

scalacOptions += "-Ypartial-unification"

libraryDependencies += "org.typelevel" %% "cats-core" % "1.4.0"

libraryDependencies += "com.typesafe.play" %% "play-json" % "2.6.10"

NesPlayJson.scala:

import cats._
import cats.data._
import cats.implicits._
import play.api.libs.functional.syntax._
import play.api.libs.json.JsArray
import play.api.libs.json.JsError
import play.api.libs.json.JsResult
import play.api.libs.json.JsResult.applicativeJsResult
import play.api.libs.json.JsValue
import play.api.libs.json.Json.toJson
import play.api.libs.json.Reads
import play.api.libs.json.Writes
import play.api.libs.json._

object NesPlayJson {

  // --- NEL, details: https://www.cakesolutions.net/teamblogs/how-to-use-nonemptylist-in-a-model-class
  implicit object jsResultApplicative extends Applicative[JsResult] {
    override def pure[A](x: A): JsResult[A] = applicativeJsResult.pure(x)

    override def ap[A, B](ff: JsResult[A => B])(fa: JsResult[A]): JsResult[B] = applicativeJsResult(ff, fa)
  }

  implicit final def nelReads[T: Reads]: Reads[NonEmptyList[T]] =
    Reads {
      case JsArray(values) =>
        values.toList match {
          case head :: tail => NonEmptyList.of(head.validate[T], tail.map(_.validate[T]): _*).sequence
          case Nil          => JsError("expected a NonEmptyList but got empty list")
        }
      case other: JsValue => JsError(s"expected an array but got $other")
    }

  implicit final def nelWrites[T: Writes]: Writes[NonEmptyList[T]] =
    Writes(nel => toJson(nel.toList))

  // --- NES.  
  implicit final def nesFormat[T: Format: Order]: Format[NonEmptySet[T]] =
    implicitly[Format[NonEmptyList[T]]].inmap(nel => NonEmptySet.of(nel.head, nel.tail: _*), _.toNonEmptyList)

  // --- Test.

  final case class MyCaseClass(values: NonEmptySet[String], foo: Int)

  object MyCaseClass {
    implicit val MyCaseClassFmt: OFormat[MyCaseClass] = Json.format
  }

  def main(args: Array[String]): Unit =
    println(Json.toJson(MyCaseClass(NonEmptySet.of("a", "b", "c"), 42)))
}

Compile error:

Error:(46, 62) No instance of play.api.libs.json.Format is available for
cats.data.Newtype.Type[java.lang.String] in the implicit scope 

    implicit val MyCaseClassFmt: OFormat[MyCaseClass] = Json.format

Gitter:

@kailuowang
Copy link
Contributor

try write implicitly[Format[NonEmptyList[String]]] and nesFormat[String] directly see what error you get.

@ronanM
Copy link
Author

ronanM commented Oct 23, 2018

I changed to:

  object MyCaseClass {
    implicitly[Format[NonEmptyList[String]]]
    nesFormat[String]

    implicit val MyCaseClassFmt: OFormat[MyCaseClass] = Json.format
  }

Same compile error.

@kailuowang
Copy link
Contributor

looks like that play.json's Json.format macro failed to pick up the implicit instance in scope. As a last attempt, try create a type alias for NonEmptySet[String] and explicitly create an implicit format for that type alias, and use that type alias in the case class. if that still doesn't work, you might need to manually create a Format for your case class using combinators.

@ronanM
Copy link
Author

ronanM commented Oct 23, 2018

I changed to:

  type NesString = NonEmptySet[String]
  
  implicit val nesStringFormat : Format[NesString] = nesFormat[String]
  
  final case class MyCaseClass(values: NesString, foo: Int)

Same compile error.

@ronanM
Copy link
Author

ronanM commented Oct 24, 2018

Thank you @kailuowang.

The workaround using manual Format combinators:

  final case class MyCaseClass(values: NonEmptySet[String], foo: Int)

  object MyCaseClass {

    implicit val MyCaseClassFmt: OFormat[MyCaseClass] =
      ((__ \ "values").format[NonEmptySet[String]] and
        (__ \ "foo").format[Int])(MyCaseClass.apply, unlift(MyCaseClass.unapply))
  }

  def main(args: Array[String]): Unit = {
    val myCaseClass = MyCaseClass(NonEmptySet.of("a", "b", "c"), 42)
    val jsonStr     = Json.prettyPrint(Json.toJson(myCaseClass))
    println(jsonStr)
    println(Json.fromJson[MyCaseClass](Json.parse(jsonStr)))
  }

@armanbilge
Copy link
Member

This seems stale with nothing actionable to do in cats. Feel free to re-open.

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