Skip to content

Commit

Permalink
Merge pull request #85 from ncreep/fix-flat-format-bug
Browse files Browse the repository at this point in the history
Fixing support for recursive ADTs and user-defined implicits
  • Loading branch information
julienrf authored May 14, 2021
2 parents f1ba4e6 + 7026793 commit aa0c78a
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 6 deletions.
8 changes: 7 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import sbtcrossproject.CrossPlugin.autoImport.{crossProject, CrossType}
import com.typesafe.tools.mima.core.{IncompatibleSignatureProblem, ProblemFilters}
import sbtcrossproject.CrossPlugin.autoImport.{CrossType, crossProject}

ThisBuild / organization := "org.julienrf"

Expand All @@ -10,6 +11,11 @@ ThisBuild / versionPolicyIntention := Compatibility.BinaryAndSourceCompatible
// Temporary, because version 10.0.0 was invalid
ThisBuild / versionPolicyPreviousVersions := Seq("10.0.1")

ThisBuild / mimaBinaryIssueFilters ++= Seq(
// package private method
ProblemFilters.exclude[IncompatibleSignatureProblem]("julienrf.json.derived.DerivedOWritesUtil.makeCoProductOWrites")
)

ThisBuild / developers := List(
Developer(
"julienrf",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ trait DerivedOWritesInstances extends DerivedOWritesInstances1 {
typeTag: TT[FieldType[K, L]]
): DerivedOWrites[FieldType[K, L] :+: R, TT] =
DerivedOWritesUtil.makeCoProductOWrites(
(_, _) => writesL.value,
(_, _) => writesL,
owritesR,
typeTag)
}
Expand Down Expand Up @@ -77,7 +77,7 @@ trait DerivedOWritesInstances1 extends DerivedOWritesInstances2 {
typeTag: TT[FieldType[K, L]]
): DerivedOWrites[FieldType[K, L] :+: R, TT] =
DerivedOWritesUtil.makeCoProductOWrites(
owritesL.value.owrites,
(tagOwrites, adapter) => owritesL.map(_.owrites(tagOwrites, adapter)),
owritesR,
typeTag)
}
Expand Down Expand Up @@ -116,7 +116,7 @@ trait DerivedOWritesInstances3 {

private[derived] object DerivedOWritesUtil {
def makeCoProductOWrites[K <: Symbol, L, R <: Coproduct, TT[A] <: TypeTag[A]](
makeWritesL: (TypeTagOWrites, NameAdapter) => Writes[L],
makeWritesL: (TypeTagOWrites, NameAdapter) => Lazy[Writes[L]],
owritesR: Lazy[DerivedOWrites[R, TT]],
typeTag: TT[FieldType[K, L]]
): DerivedOWrites[FieldType[K, L] :+: R, TT] =
Expand All @@ -127,7 +127,7 @@ private[derived] object DerivedOWritesUtil {
val derivedOwriteR = owritesR.value.owrites(tagOwrites, adapter)

OWrites[FieldType[K, L] :+: R] {
case Inl(l) => tagOwrites.owrites(typeTag.value, writesL).writes(l)
case Inl(l) => tagOwrites.owrites(typeTag.value, writesL.value).writes(l)
case Inr(r) => derivedOwriteR.writes(r)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,6 @@ object TypeTagReads {
*
* @param tagReads A way to decode the type tag value.
*/
//TODO docs on edge case
def flat(tagReads: Reads[String]): TypeTagReads =
new TypeTagReads {
def reads[A](typeName: String, reads: Reads[A]): Reads[A] = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,5 +269,61 @@ class DerivedOFormatSuite extends AnyFeatureSpec with Checkers {
assert(json == Json.obj("type" -> "Bar", "__syntheticWrap__" -> JsNumber(42)))
assert(fooFormat.reads(json).asEither == Right(foo))
}

import TestHelpers._
val adt = Z(X(1), Y("VVV"))

Scenario("supports user-defined recursive formats - nested") {
val adtFormat: Format[ADTBase] = {
implicit val f1: Format[X] = Json.format
implicit val f2: Format[Y] = Json.format
implicit lazy val f3: Format[Z] = Json.format

implicit lazy val f4: Format[ADTBase] = oformat[ADTBase]()

f4
}

val json = adtFormat.writes(adt)
val obj = Json.obj(
"Z" -> Json.obj(
"l" -> Json.obj("X" -> Json.obj("a" -> 1)),
"r" -> Json.obj("Y" -> Json.obj("b" -> "VVV"))))

assert(json == obj)
assert(adtFormat.reads(json).asEither == Right(adt))
}

Scenario("supports user-defined recursive formats - flat") {
val adtFormat: Format[ADTBase] = {
implicit val f1: Format[X] = Json.format
implicit val f2: Format[Y] = Json.format
implicit lazy val f3: Format[Z] = Json.format

implicit lazy val f4: Format[ADTBase] = flat.oformat((__ \ "type").format[String])

f4
}

val json = adtFormat.writes(adt)
val obj = Json.obj(
"type" -> "Z",
"l" -> Json.obj("type" -> "X", "a" -> 1),
"r" -> Json.obj("type" -> "Y", "b" -> "VVV"))

assert(json == obj)
assert(adtFormat.reads(json).asEither == Right(adt))
}
}
}


object TestHelpers {
// Placing it here in a separate object since otherwise the Json.format macro fails to compile
// for these types
sealed trait ADTBase

case class X(a: Int) extends ADTBase
case class Y(b: String) extends ADTBase
case class Z(l: ADTBase, r: ADTBase) extends ADTBase
}

0 comments on commit aa0c78a

Please sign in to comment.