Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .sbtopts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-Dsbt.io.implicit.relative.glob.conversion=allow
16 changes: 9 additions & 7 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ lazy val `jackson-212` = project
"com.fasterxml.jackson.core" % "jackson-core" % "2.12.7"
)
)
.dependsOn(core)
.dependsOn(core % "compile->compile;test->test")

lazy val `jackson-213` = project
.in(jackson / "jackson-213")
Expand All @@ -239,7 +239,7 @@ lazy val `jackson-213` = project
"com.fasterxml.jackson.core" % "jackson-core" % "2.13.5"
)
)
.dependsOn(core)
.dependsOn(core % "compile->compile;test->test")

lazy val `jackson-214` = project
.in(jackson / "jackson-214")
Expand All @@ -253,7 +253,7 @@ lazy val `jackson-214` = project
"com.fasterxml.jackson.core" % "jackson-core" % "2.14.3"
)
)
.dependsOn(core)
.dependsOn(core % "compile->compile;test->test")

lazy val `jackson-215` = project
.in(jackson / "jackson-215")
Expand All @@ -267,7 +267,7 @@ lazy val `jackson-215` = project
"com.fasterxml.jackson.core" % "jackson-core" % "2.15.4"
)
)
.dependsOn(core)
.dependsOn(core % "compile->compile;test->test")

lazy val `jackson-216` = project
.in(jackson / "jackson-216")
Expand All @@ -281,7 +281,7 @@ lazy val `jackson-216` = project
"com.fasterxml.jackson.core" % "jackson-core" % "2.16.2"
)
)
.dependsOn(core)
.dependsOn(core % "compile->compile;test->test")

lazy val `jackson-217` = project
.in(jackson / "jackson-217")
Expand All @@ -295,7 +295,7 @@ lazy val `jackson-217` = project
"com.fasterxml.jackson.core" % "jackson-core" % "2.17.3"
)
)
.dependsOn(core)
.dependsOn(core % "compile->compile;test->test")

lazy val `jackson-218` = project
.in(jackson / "jackson-218")
Expand All @@ -309,7 +309,7 @@ lazy val `jackson-218` = project
"com.fasterxml.jackson.core" % "jackson-core" % "2.18.3"
)
)
.dependsOn(core)
.dependsOn(core % "compile->compile;test->test")

lazy val benchmarks = project
.in(modules / "benchmarks")
Expand All @@ -327,6 +327,8 @@ lazy val benchmarks = project
"io.circe" %% "circe-jackson210" % "0.14.0",
"dev.zio" %% "zio-json" % "0.7.1",
"com.typesafe.play" %% "play-json" % "2.10.5",
"com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-core" % "2.33.1",
"com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-macros" % "2.33.1",
"org.knowm.xchart" % "xchart" % "3.8.2" exclude ("de.erichseifert.vectorgraphics2d", "VectorGraphics2D") withSources ()
),
scalacOptions ++= {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,7 @@ class JacksonTokenWriter(jsonGenerator: JsonGenerator) extends TokenWriter {
override def close(): Unit = jsonGenerator.close()

override def flush(): Unit = jsonGenerator.flush()

override def result(): String =
jsonGenerator.getOutputTarget.toString
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,66 @@
package tethys

import java.io.{Reader, Writer}

import com.fasterxml.jackson.core.JsonFactory
import java.io.Reader
import com.fasterxml.jackson.core.{
JsonFactory,
JsonFactoryBuilder,
JsonGenerator
}
import tethys.readers.{FieldName, ReaderError}
import tethys.readers.tokens.{TokenIterator, TokenIteratorProducer}
import tethys.writers.tokens.{TokenWriter, TokenWriterProducer}
import tethys.writers.tokens.{
TokenWriter,
TokenWriterConfig,
TokenWriterProducer
}

package object jackson {
lazy val defaultJsonFactory: JsonFactory = {
val f = new JsonFactory()
f.configure(JsonFactory.Feature.INTERN_FIELD_NAMES, false)
f
lazy val defaultJsonFactory: JsonFactory =
new JsonFactoryBuilder()
.configure(JsonFactory.Feature.INTERN_FIELD_NAMES, false)
.build()

class JacksonTokenWriterProducer(
jsonFactory: JsonFactory,
// used for compatibility where tethys.jackson.pretty import is required
modifyConfig: TokenWriterConfig => TokenWriterConfig
) extends TokenWriterProducer {

private def updateGeneratorFromConfig(
generator: JsonGenerator,
config: TokenWriterConfig
): JsonGenerator = {
if (config == TokenWriterConfig.default)
generator
else {
val first =
if (config.indentionStep == 2)
generator.useDefaultPrettyPrinter()
else
generator

val second =
if (config.escapeUnicode)
first.setHighestNonEscapedChar(127)
else
first
second
}
}

override def produce(config: TokenWriterConfig): TokenWriter = {
val generator = updateGeneratorFromConfig(
jsonFactory.createGenerator(new java.io.StringWriter()),
modifyConfig(config)
)
new JacksonTokenWriter(generator)
}
}

implicit def jacksonTokenWriterProducer(implicit
jsonFactory: JsonFactory = defaultJsonFactory
): TokenWriterProducer = new TokenWriterProducer {
override def forWriter(writer: Writer): TokenWriter = {
new JacksonTokenWriter(jsonFactory.createGenerator(writer))
}
}
): JacksonTokenWriterProducer =
new JacksonTokenWriterProducer(jsonFactory, identity)

implicit def jacksonTokenIteratorProducer(implicit
jsonFactory: JsonFactory = defaultJsonFactory
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
package tethys.jackson

import java.io.Writer

import com.fasterxml.jackson.core.JsonFactory
import tethys.readers.tokens.TokenIteratorProducer
import tethys.writers.tokens.{TokenWriter, TokenWriterProducer}

package object pretty {

@deprecated("Provide implicit TokenWriterConfig to `asJson` instead")
implicit def prettyJacksonTokenWriterProducer(implicit
jsonFactory: JsonFactory = defaultJsonFactory
): TokenWriterProducer = new TokenWriterProducer {
override def forWriter(writer: Writer): TokenWriter = {
new JacksonTokenWriter(
jsonFactory.createGenerator(writer).useDefaultPrettyPrinter()
)
}
}
): JacksonTokenWriterProducer =
new tethys.jackson.JacksonTokenWriterProducer(
jsonFactory,
modifyConfig = _.withDefaultPrettyPrinter
)

implicit def jacksonTokenIteratorProducer(implicit
jsonFactory: JsonFactory = defaultJsonFactory
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
package tethys.jackson

import tethys.writers.tokens.{
TokenWriterProducer,
TokenWriterConfig,
JsonWriterSpec
}
import tethys._

class JacksonJsonWriterSpec extends JsonWriterSpec {
{
import tethys.jackson.pretty._
import tethys.writers.tokens.TestModels._

configurableTestCase(
description = "pretty print complex structures",
config = TokenWriterConfig.default.withDefaultPrettyPrinter,
value = Department(
"Global Engineering",
List(
Employee(
1L,
Person("John Doe", 30, Some("john@example.com")),
Address("123 Main St", "New York", "USA", Some("10001")),
"Backend",
BigDecimal("100000.00")
),
Employee(
2L,
Person("Jane Smith", 28, None),
Address("456 Park Ave", "Boston", "USA", None),
"Frontend",
BigDecimal("95000.50")
)
),
Set("global", "engineering", "tech"),
Map(
"headquarters" -> "New York",
"founded" -> "2020",
"status" -> "active"
)
),
json = """{
"name" : "Global Engineering",
"employees" : [ {
"id" : 1,
"person" : {
"name" : "John Doe",
"age" : 30,
"email" : "john@example.com"
},
"address" : {
"street" : "123 Main St",
"city" : "New York",
"country" : "USA",
"postalCode" : "10001"
},
"department" : "Backend",
"salary" : 100000.00
}, {
"id" : 2,
"person" : {
"name" : "Jane Smith",
"age" : 28
},
"address" : {
"street" : "456 Park Ave",
"city" : "Boston",
"country" : "USA"
},
"department" : "Frontend",
"salary" : 95000.50
} ],
"tags" : [ "global", "engineering", "tech" ],
"metadata" : {
"headquarters" : "New York",
"founded" : "2020",
"status" : "active"
}
}"""

)

configurableTestCase(
description = "escape unicode",
config = TokenWriterConfig.default.withEscapeUnicode(true),
value = "Unicode: \u00a9 \u20ac \u2603",
json = "\"Unicode: \\u00A9 \\u20AC \\u2603\""
)

configurableTestCase(
description = "escape unicode emoji",
config = TokenWriterConfig.default.withEscapeUnicode(true),
value = "Emoji: 😀 🚀 🌍",
json = "\"Emoji: \\uD83D\\uDE00 \\uD83D\\uDE80 \\uD83C\\uDF0D\""
)

configurableTestCase(
description = "unicode characters in object keys",
config = TokenWriterConfig.default.withEscapeUnicode(true),
value = Map("café" -> "coffee", "résumé" -> "CV", "über" -> "super"),
json = "{\"caf\\u00E9\":\"coffee\",\"r\\u00E9sum\\u00E9\":\"CV\",\"\\u00FCber\":\"super\"}"
)

case class MenuItem(name: String, price: Double)
case class Menu(items: List[MenuItem])
case class Restaurant(menu: Menu)

implicit val menuItemWriter: JsonWriter[MenuItem] = JsonWriter.obj[MenuItem]
.addField("name")(_.name)
.addField("price")(_.price)

implicit val menuWriter: JsonWriter[Menu] = JsonWriter.obj[Menu]
.addField("items")(_.items)

implicit val restaurantWriter: JsonWriter[Restaurant] = JsonWriter.obj[Restaurant]
.addField("menu")(_.menu)

configurableTestCase(
description = "unicode characters in nested objects",
config = TokenWriterConfig.default.withEscapeUnicode(true),
value = Restaurant(Menu(List(
MenuItem("Café au lait", 3.50),
MenuItem("Crème brûlée", 5.75)
))),
json = "{\"menu\":{\"items\":[{\"name\":\"Caf\\u00E9 au lait\",\"price\":3.5},{\"name\":\"Cr\\u00E8me br\\u00FBl\\u00E9e\",\"price\":5.75}]}}"
)

configurableTestCase(
description = "mixed ASCII and unicode in arrays",
config = TokenWriterConfig.default.withEscapeUnicode(true),
value = List("hello", "世界", "こんにちは", "안녕하세요"),
json = "[\"hello\",\"\\u4E16\\u754C\",\"\\u3053\\u3093\\u306B\\u3061\\u306F\",\"\\uC548\\uB155\\uD558\\uC138\\uC694\"]"
)

configurableTestCase(
description = "control characters that should always be escaped",
config = TokenWriterConfig.default.withEscapeUnicode(false),
value = "Control chars: \u0000\u0001\u0008\u0009\u000A\u000C\u000D\u001F",
json = "\"Control chars: \\u0000\\u0001\\b\\t\\n\\f\\r\\u001F\""
)

configurableTestCase(
description = "unicode characters with escapeUnicode set to false",
config = TokenWriterConfig.default.withEscapeUnicode(false),
value = "Unicode: \u00a9 \u20ac \u2603",
json = "\"Unicode: © € ☃\""
)

case class SurrogatePairsTest(text: String, list: List[String], keyValue: (String, String))

implicit val surrogatePairsTestWriter: JsonWriter[SurrogatePairsTest] = JsonWriter.obj[SurrogatePairsTest]
.addField("text")(_.text)
.addField("list")(_.list)
.addField("nested")(test => Map(test.keyValue))

configurableTestCase(
description = "surrogate pairs in different contexts",
config = TokenWriterConfig.default.withEscapeUnicode(true),
value = SurrogatePairsTest(
"Surrogate pairs: 𝄞 𝌆 𝓐",
List("𝄞", "𝌆", "𝓐"),
("key𝄞", "value𝌆")
),
json = "{\"text\":\"Surrogate pairs: \\uD834\\uDD1E \\uD834\\uDF06 \\uD835\\uDCD0\",\"list\":[\"\\uD834\\uDD1E\",\"\\uD834\\uDF06\",\"\\uD835\\uDCD0\"],\"nested\":{\"key\\uD834\\uDD1E\":\"value\\uD834\\uDF06\"}}"
)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package tethys.jackson

import tethys._
import tethys.writers.tokens.{TokenWriterSpec, TokenWriterProducer}

class JacksonTokenWriterSpec extends TokenWriterSpec
Loading
Loading