Skip to content

Commit

Permalink
stacksafe play -> circe AST conversion
Browse files Browse the repository at this point in the history
  • Loading branch information
Maksym Fedorov committed Jan 31, 2023
1 parent 6b81358 commit 8858fd0
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 24 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.evolutiongaming.util

import cats.Eval
import io.circe.{Json => CirceJson}
import play.api.libs.{json => PlayJson}

Expand All @@ -14,28 +15,41 @@ object PlayCirceAstConversions {
jsonObject = o => PlayJson.JsObject(o.toIterable.map { case (k, v) => (k, circeToPlay(v)) }.toSeq),
)

def playToCirce(value: PlayJson.JsValue): CirceJson =
value match {
case PlayJson.JsNull =>
CirceJson.Null

case PlayJson.JsTrue =>
CirceJson.True

case PlayJson.JsFalse =>
CirceJson.False

case PlayJson.JsNumber(value) =>
CirceJson.fromBigDecimal(value)

case PlayJson.JsString(value) =>
CirceJson.fromString(value)

case PlayJson.JsArray(value) =>
CirceJson.fromValues(value.map(playToCirce))

case PlayJson.JsObject(value) =>
CirceJson.fromFields(value.iterator.map { case (k, v) => (k, playToCirce(v)) }.to(Iterable))
}
def playToCirce(value: PlayJson.JsValue): CirceJson = {
def inner(value: Eval[PlayJson.JsValue]): Eval[CirceJson] =
value.flatMap {
case PlayJson.JsNull =>
Eval.now(CirceJson.Null)

case PlayJson.JsTrue =>
Eval.now(CirceJson.True)

case PlayJson.JsFalse =>
Eval.now(CirceJson.False)

case PlayJson.JsNumber(value) =>
Eval.now(CirceJson.fromBigDecimal(value))

case PlayJson.JsString(value) =>
Eval.now(CirceJson.fromString(value))

case PlayJson.JsArray(values) =>
if (values.isEmpty)
Eval.now(CirceJson.arr())
else
values.map(v => inner(Eval.now(v)))
.foldLeft(Eval.now(Vector.empty[CirceJson]))((acc, v) => v.flatMap(v => acc.map(_ :+ v)))
.map(CirceJson.fromValues)

case PlayJson.JsObject(value) =>
Eval.defer {
value.view.map { case (k, v) =>
inner(Eval.now(v)).map(k -> _)
}.foldLeft(Eval.now(Map.empty[String, CirceJson]))((acc, v) => v.flatMap(v => acc.map(_ + v)))
.map(CirceJson.fromFields)
}
}
inner(Eval.now(value)).value
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import play.api.libs.{json => play}
import org.scalatest.prop.TableDrivenPropertyChecks


class PlayCirceAstConversionsSpec extends AnyFreeSpec with TableDrivenPropertyChecks with Matchers {
class PlayCirceAstConversionsSmokeSpec extends AnyFreeSpec with TableDrivenPropertyChecks with Matchers {

"Play to/from Circe AST conversions" in {
forAll {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.evolutiongaming.util

import com.evolutiongaming.util.PlayCirceAstConversions._
import org.scalatest.freespec.AnyFreeSpec
import org.scalatest.matchers.should.Matchers
import org.scalatest.prop.TableDrivenPropertyChecks
import play.api.libs.json.{JsObject, Json}

class PlayCirceAstConversionsStackSafetySpec extends AnyFreeSpec with TableDrivenPropertyChecks with Matchers {
"Play to Circe AST conversion" - {
"can handle deeply nested structures" in {
val playJson = (1 to 100000).foldLeft(JsObject.empty)((o, _) => Json.obj("n" -> o))
assert(playToCirce(playJson).isObject)
}

"can handle long arrays" in {
val playJson = Json.arr(1 to 100000)
assert(playToCirce(playJson).isArray)
}
}
}

0 comments on commit 8858fd0

Please sign in to comment.