Skip to content

Commit

Permalink
Json4s compat cross building with json4s 3 and 4 (#51)
Browse files Browse the repository at this point in the history
  • Loading branch information
nrktkt authored Jan 12, 2023
1 parent 88868c7 commit dc0956f
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 82 deletions.
2 changes: 1 addition & 1 deletion .mill-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.9.11
0.9.12
21 changes: 14 additions & 7 deletions build.sc
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
)
Expand All @@ -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")
Expand Down
57 changes: 30 additions & 27 deletions json4s-compat/src/nrktkt/ninny/compat/Json4sCompat.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
Expand Down
127 changes: 80 additions & 47 deletions json4s-compat/test/src/nrktkt/ninny/compat/Json4sCompatSpec.scala
Original file line number Diff line number Diff line change
@@ -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
}
}

0 comments on commit dc0956f

Please sign in to comment.