diff --git a/circe/src/main/scala/com/kevel/apso/circe/Implicits.scala b/circe/src/main/scala/com/kevel/apso/circe/Implicits.scala index 79699b18..7e989c6d 100644 --- a/circe/src/main/scala/com/kevel/apso/circe/Implicits.scala +++ b/circe/src/main/scala/com/kevel/apso/circe/Implicits.scala @@ -1,5 +1,6 @@ package com.kevel.apso.circe +import scala.annotation.tailrec import scala.util.Try import io.circe._ @@ -121,11 +122,17 @@ object Implicits { } } - paths match { - case Nil => Json.obj() - case (path, value) :: rem => - createJson(path.split(separatorRegex).toList, value).deepMerge(fromFullPaths(rem, separatorRegex)) + @tailrec + def fromFullPathsRec(paths: Seq[(String, Json)], acc: Json): Json = { + paths match { + case Nil => acc + case (path, value) :: rem => + val newAcc = acc.deepMerge(createJson(path.split(separatorRegex).toList, value)) + fromFullPathsRec(rem, newAcc) + } } + + fromFullPathsRec(paths, Json.obj()) } final implicit class ApsoJsonEncoder[A](val encoder: Encoder[A]) extends AnyVal { diff --git a/circe/src/test/scala/com/kevel/apso/circe/ImplicitsSpec.scala b/circe/src/test/scala/com/kevel/apso/circe/ImplicitsSpec.scala index 254efea7..810e2504 100644 --- a/circe/src/test/scala/com/kevel/apso/circe/ImplicitsSpec.scala +++ b/circe/src/test/scala/com/kevel/apso/circe/ImplicitsSpec.scala @@ -26,6 +26,19 @@ class ImplicitsSpec extends Specification { res mustEqual expectedJson } + + "not throw a StackOverflowError for large lists of paths" in { + val paths = (1 to 10000).map(i => (s"a.v$i", i.asJson)).toList + fromFullPaths(paths, ".") must not(throwAn[StackOverflowError]) + } + + "giving precedence to the last path value if duplicate paths exist" in { + val json1 = fromFullPaths(List("a" -> 1.asJson, "a" -> 2.asJson, "a" -> 3.asJson)) + json1 mustEqual json"""{"a": 3}""" + + val json2 = fromFullPaths(List("a.b.c" -> 1.asJson, "a.b" -> 2.asJson, "a" -> 3.asJson)) + json2 mustEqual json"""{"a": 3}""" + } } "provide a method to get the key set of a JSON Object" in {