diff --git a/.mill-version b/.mill-version index 6889a31..bf1ba0c 100644 --- a/.mill-version +++ b/.mill-version @@ -1 +1 @@ -0.9.11 \ No newline at end of file +0.9.12 \ No newline at end of file diff --git a/build.sc b/build.sc index 9ff7e0f..9991341 100644 --- a/build.sc +++ b/build.sc @@ -8,11 +8,11 @@ import mill.define.{Segment, Segments} import $file.forProductN val `2.12` = "2.12.15" -val `2.13` = "2.13.8" +val `2.13` = "2.13.10" val `3` = "3.1.0" val scalaTest = ivy"org.scalatest::scalatest:3.2.10" -val json4sVersion = "4.0.3" +val json4sVersion = Map(4 -> "4.0.6", 3 -> "3.6.12") trait PublishMod extends PublishModule { def sonatypeUri = "https://s01.oss.sonatype.org/service/local" @@ -88,7 +88,7 @@ class Ninny(val crossScalaVersion: String) T(self.scalacOptions().filterNot(_ == "-Xfatal-warnings")) def ivyDeps = Agg( - ivy"org.json4s::json4s-native-core:$json4sVersion", + ivy"org.json4s::json4s-native-core:${json4sVersion(4)}", ivy"org.slf4j:slf4j-simple:1.7.32", scalaTest ) @@ -113,15 +113,22 @@ class PlayCompat(val crossScalaVersion: String) } } -object `json4s-compat` extends mill.Cross[Json4sCompat](`2.12`, `2.13`) -class Json4sCompat(val crossScalaVersion: String) +val json4sCrossMatrix = for { + json4sVersion <- Seq(4, 3) + scalaVersion <- Seq(`2.12`, `2.13`) ++ (if (json4sVersion >= 4) Seq(`3`) + else Nil) +} yield (scalaVersion, json4sVersion) +object `json4s-compat` extends mill.Cross[Json4sCompat](json4sCrossMatrix: _*) +class Json4sCompat(val crossScalaVersion: String, val json4sMajor: Int) extends CrossScalaModule with PublishMod { - def artifactName = "ninny-json4s-compat" + def millSourcePath = super.millSourcePath / os.up + + def artifactName = s"ninny-json4s$json4sMajor-compat" def moduleDeps = List(ninny(crossScalaVersion)) - def ivyDeps = Agg(ivy"org.json4s::json4s-ast:$json4sVersion") + def ivyDeps = Agg(ivy"org.json4s::json4s-ast:${json4sVersion(json4sMajor)}") object test extends Tests with TestModule.ScalaTest { def testFrameworks = Seq("org.scalatest.tools.Framework") diff --git a/json4s-compat/src/nrktkt/ninny/compat/Json4sCompat.scala b/json4s-compat/src/nrktkt/ninny/compat/Json4sCompat.scala index f36667d..c183143 100644 --- a/json4s-compat/src/nrktkt/ninny/compat/Json4sCompat.scala +++ b/json4s-compat/src/nrktkt/ninny/compat/Json4sCompat.scala @@ -16,7 +16,7 @@ object Json4sCompat { obj(pair._1 -> pair._2, newPair._1 -> newPair._2) } - implicit class JsonObjectSyntax(json: JsonObject) extends { + implicit class JsonObjectSyntax(json: JsonObject) { def ~[B: ToJson](pair: (String, B)): JsonObject = json + pair def ~(other: JsonObject): JsonObject = json ++ other } @@ -33,37 +33,40 @@ object Json4sCompat { def extractOpt[A: FromJson]: Option[A] = json.to[Option[A]].get } - def toJson4s(json: JsonValue): org.json4s.JValue = json match { - case JsonArray(values) => org.json4s.JArray(values.map(toJson4s).toList) - case blob: JsonBlob => org.json4s.JString(Json.render(blob)) - case JsonDecimal(preciseValue) => org.json4s.JDecimal(preciseValue) - case JsonDouble(value) => org.json4s.JDouble(value) - case boolean: JsonBoolean => org.json4s.JBool(boolean.value) - case JsonNull => org.json4s.JNull - case JsonString(value) => org.json4s.JString(value) + def toJson4s(json: JsonValue): org.json4s.JsonAST.JValue = json match { + case JsonArray(values) => + org.json4s.JsonAST.JArray(values.map(toJson4s).toList) + case blob: JsonBlob => org.json4s.JsonAST.JString(Json.render(blob)) + case JsonDecimal(preciseValue) => org.json4s.JsonAST.JDecimal(preciseValue) + case JsonDouble(value) => org.json4s.JsonAST.JDouble(value) + case boolean: JsonBoolean => org.json4s.JsonAST.JBool(boolean.value) + case JsonNull => org.json4s.JsonAST.JNull + case JsonString(value) => org.json4s.JsonAST.JString(value) case JsonObject(values) => - org.json4s.JObject(values.map { case (name, value) => + org.json4s.JsonAST.JObject(values.map { case (name, value) => name -> toJson4s(value) }.toList) } - def toNinnyJson(json: org.json4s.JValue): Option[JsonValue] = json match { - case org.json4s.JNothing => None - case org.json4s.JNull => Some(JsonNull) - case org.json4s.JString(s) => Some(JsonString(s)) - case org.json4s.JDouble(num) => Some(JsonDouble(num)) - case org.json4s.JDecimal(num) => Some(JsonDecimal(num)) - case org.json4s.JLong(num) => Some(JsonDouble(num)) - case org.json4s.JInt(num) => Some(JsonDecimal(BigDecimal(num))) - case org.json4s.JBool(value) => Some(JsonBoolean(value)) - case org.json4s.JArray(arr) => Some(JsonArray(arr.flatMap(toNinnyJson))) - case org.json4s.JSet(set) => - Some(JsonArray(set.toList.flatMap(toNinnyJson _))) - case org.json4s.JObject(obj) => - Some(JsonObject(obj.flatMap { case (name, value) => - toNinnyJson(value).map(name -> _) - }.toMap)) - } + def toNinnyJson(json: org.json4s.JsonAST.JValue): Option[JsonValue] = + json match { + case org.json4s.JsonAST.JNothing => None + case org.json4s.JsonAST.JNull => Some(JsonNull) + case org.json4s.JsonAST.JString(s) => Some(JsonString(s)) + case org.json4s.JsonAST.JDouble(num) => Some(JsonDouble(num)) + case org.json4s.JsonAST.JDecimal(num) => Some(JsonDecimal(num)) + case org.json4s.JsonAST.JLong(num) => Some(JsonDouble(num)) + case org.json4s.JsonAST.JInt(num) => Some(JsonDecimal(BigDecimal(num))) + case org.json4s.JsonAST.JBool(value) => Some(JsonBoolean(value)) + case org.json4s.JsonAST.JArray(arr) => + Some(JsonArray(arr.flatMap(toNinnyJson))) + case org.json4s.JsonAST.JSet(set) => + Some(JsonArray(set.toList.flatMap(toNinnyJson _))) + case org.json4s.JsonAST.JObject(obj) => + Some(JsonObject(obj.flatMap { case (name, value) => + toNinnyJson(value).map(name -> _) + }.toMap)) + } object ast { type JValue = JsonValue diff --git a/json4s-compat/test/src/nrktkt/ninny/compat/Json4sCompatSpec.scala b/json4s-compat/test/src/nrktkt/ninny/compat/Json4sCompatSpec.scala index 3cce4bb..c5b0bc0 100644 --- a/json4s-compat/test/src/nrktkt/ninny/compat/Json4sCompatSpec.scala +++ b/json4s-compat/test/src/nrktkt/ninny/compat/Json4sCompatSpec.scala @@ -1,68 +1,101 @@ package nrktkt.ninny.compat -import org.json4s._ - import nrktkt.ninny.ast._ import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should import org.scalatest.OptionValues +import org.scalatest.TryValues class Json4sCompatSpec extends AnyFlatSpec with should.Matchers - with OptionValues { + with OptionValues + with TryValues { + { + import org.json4s.JsonAST._ + "Conversion from json4s to ninny" should "return None on JNothing" in { + Json4sCompat.toNinnyJson(JNothing) shouldEqual None + } - "Conversion from json4s to ninny" should "return None on JNothing" in { - Json4sCompat.toNinnyJson(JNothing) shouldEqual None - } + it should "convert all types" in { + val json4s = JObject( + "absent" -> JNothing, + "long" -> JLong(1), + "bigInt" -> JInt(1), + "exactNumber" -> JDecimal(BigDecimal("1.23")), + "bool" -> JBool(true), + "string" -> JString("value"), + "null" -> JNull, + "array" -> JArray(List(JNull)) + ) - it should "convert all types" in { - val json4s = JObject( - "absent" -> JNothing, - "long" -> JLong(1), - "bigInt" -> JInt(1), - "exactNumber" -> JDecimal(BigDecimal("1.23")), - "bool" -> JBool(true), - "string" -> JString("value"), - "null" -> JNull, - "array" -> JArray(List(JNull)) - ) + val ninny = nrktkt.ninny.obj( + "long" -> JsonDouble(1), + "bigInt" -> BigDecimal(1), + "exactNumber" -> BigDecimal("1.23"), + "bool" -> true, + "string" -> "value", + "null" -> JsonNull, + "array" -> Seq(JsonNull) + ) - val ninny = nrktkt.ninny.obj( - "long" -> JsonDouble(1), - "bigInt" -> BigDecimal(1), - "exactNumber" -> BigDecimal("1.23"), - "bool" -> true, - "string" -> "value", - "null" -> JsonNull, - "array" -> Seq(null) - ) + Json4sCompat.toNinnyJson(json4s).value shouldEqual ninny + } - Json4sCompat.toNinnyJson(json4s).value shouldEqual ninny + "Conversion from ninny to json4s" should "convert all types" in { + val ninny = nrktkt.ninny.obj( + "long" -> JsonDouble(1), + "bigInt" -> BigDecimal(1), + "exactNumber" -> BigDecimal("1.23"), + "bool" -> true, + "string" -> "value", + "null" -> JsonNull, + "array" -> Seq(JsonNull) + ) + + val json4s = JObject( + "long" -> JDouble(1), + "bigInt" -> JDecimal(1), + "exactNumber" -> JDecimal(BigDecimal("1.23")), + "bool" -> JBool(true), + "string" -> JString("value"), + "null" -> JNull, + "array" -> JArray(List(JNull)) + ) + + Json4sCompat.toJson4s(ninny) shouldEqual json4s + } } - "Conversion from ninny to json4s" should "convert all types" in { - val ninny = nrktkt.ninny.obj( - "long" -> JsonDouble(1), - "bigInt" -> BigDecimal(1), - "exactNumber" -> BigDecimal("1.23"), - "bool" -> true, - "string" -> "value", - "null" -> JsonNull, - "array" -> Seq(null) + "Json4s DSL" should "build objects" in { + // from example on json4s website + import Json4sCompat._ + case class Winner(id: Long, numbers: List[Int]) + case class Lotto( + id: Long, + winningNumbers: List[Int], + winners: List[Winner], + drawDate: Option[java.util.Date] ) - - val json4s = JObject( - "long" -> JDouble(1), - "bigInt" -> JDecimal(1), - "exactNumber" -> JDecimal(BigDecimal("1.23")), - "bool" -> JBool(true), - "string" -> JString("value"), - "null" -> JNull, - "array" -> JArray(List(JNull)) + val winners = List( + Winner(23, List(2, 45, 34, 23, 3, 5)), + Winner(54, List(52, 3, 12, 11, 18, 22)) ) - - Json4sCompat.toJson4s(ninny) shouldEqual json4s + val lotto = Lotto(5, List(2, 45, 34, 23, 7, 5, 3), winners, None) + val json: JsonObject = + ("lotto" -> + ("lotto-id" -> lotto.id) ~ + ("winning-numbers" -> lotto.winningNumbers) ~ + ("draw-date" -> lotto.drawDate.map(_.toString)) ~ + ("winners" -> + lotto.winners.map { w => + (("winner-id" -> w.id) ~ + ("numbers" -> w.numbers)) + })) ~ ("hmm, don't love that this is needed" -> None) + json.lotto.`lotto-id`.to[Long].success.value shouldEqual lotto.id + json.lotto.winners(0).numbers.to[Seq[Int]].success.value shouldEqual lotto + .winners(0) + .numbers } }