From 4c15658f781bb67d86cc7111126fda52a4ebe7c1 Mon Sep 17 00:00:00 2001 From: morazow Date: Thu, 14 Oct 2021 13:50:13 +0200 Subject: [PATCH] Fixed JSON mapper bug. Fixes #24 --- .github/workflows/ci-build.yml | 2 +- build.sbt | 2 +- doc/changes/changelog.md | 1 + doc/changes/changes_0.3.1.md | 20 ++++++ project/Dependencies.scala | 1 + .../com/exasol/common/json/JsonMapper.scala | 61 ++++++++++++++++--- .../exasol/common/json/JsonMapperTest.scala | 29 ++++++++- 7 files changed, 105 insertions(+), 11 deletions(-) create mode 100644 doc/changes/changes_0.3.1.md diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 771c59e..ad822f5 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -10,7 +10,7 @@ jobs: strategy: fail-fast: false matrix: - scala: [ 2.12.14, 2.13.6 ] + scala: [ 2.12.15, 2.13.6 ] steps: - name: Checkout the Repository diff --git a/build.sbt b/build.sbt index 1a04066..5fa086c 100644 --- a/build.sbt +++ b/build.sbt @@ -10,7 +10,7 @@ lazy val orgSettings = Seq( lazy val buildSettings = Seq( scalaVersion := "2.13.6", - crossScalaVersions := Seq("2.12.14", "2.13.6") + crossScalaVersions := Seq("2.12.15", "2.13.6") ) lazy val root = diff --git a/doc/changes/changelog.md b/doc/changes/changelog.md index c381720..1e7d076 100644 --- a/doc/changes/changelog.md +++ b/doc/changes/changelog.md @@ -1,5 +1,6 @@ # Releases +* [0.3.1](changes_0.3.1.md) * [0.3.0](changes_0.3.0.md) * [0.2.0](changes_0.2.0.md) * [0.1.0](changes_0.1.0.md) diff --git a/doc/changes/changes_0.3.1.md b/doc/changes/changes_0.3.1.md new file mode 100644 index 0000000..87202cc --- /dev/null +++ b/doc/changes/changes_0.3.1.md @@ -0,0 +1,20 @@ +# Import Export UDF Common Scala 0.3.1, released 2021-10-14 + +Code name: Fixed JSON Mapper + +## Summary + +This release fixes parsing bugs in JSON mapper functionality. + +## Bug Fixes + +* #24: Fixed JSON mapper issues + +## Dependency Updates + +### Runtime Dependency Updates + +### Test Dependency Updates + +### Plugin Updates + diff --git a/project/Dependencies.scala b/project/Dependencies.scala index f5b113e..fe36e0c 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -31,6 +31,7 @@ object Dependencies { exclude ("org.scala-lang", "scala-reflect"), "org.apache.avro" % "avro" % AvroVersion exclude ("org.slf4j", "slf4j-api") + exclude ("org.apache.commons", "commons-compress") excludeAll ( ExclusionRule(organization = "com.fasterxml.jackson.core"), ExclusionRule(organization = "com.fasterxml.jackson.module") diff --git a/src/main/scala/com/exasol/common/json/JsonMapper.scala b/src/main/scala/com/exasol/common/json/JsonMapper.scala index a473e94..a80c9fe 100644 --- a/src/main/scala/com/exasol/common/json/JsonMapper.scala +++ b/src/main/scala/com/exasol/common/json/JsonMapper.scala @@ -1,20 +1,67 @@ package com.exasol.common.json -import com.fasterxml.jackson.core.`type`.TypeReference +import com.fasterxml.jackson.core.JsonParser +import com.fasterxml.jackson.databind.MapperFeature +import com.fasterxml.jackson.databind.SerializationFeature import com.fasterxml.jackson.databind.json.{JsonMapper => BaseJsonMapper} -import com.fasterxml.jackson.module.scala.DefaultScalaModule +import com.fasterxml.jackson.module.scala.ClassTagExtensions +/** + * JSON parsing helper object class. + */ object JsonMapper { - private[this] val mapper = BaseJsonMapper - .builder() - .addModule(DefaultScalaModule) - .build() + private[this] class ScalaJsonMapper(jsonMapper: BaseJsonMapper) + extends BaseJsonMapper(jsonMapper) + with ClassTagExtensions + private[this] val mapper = { + val builder = BaseJsonMapper + .builder() + .findAndAddModules() + .enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS) + .enable(JsonParser.Feature.ALLOW_COMMENTS) + .defaultMergeable(true) + new ScalaJsonMapper(builder.build()) + } + + /** + * Parses given value type into a JSON string. + * + * For parsing into pretty indented JSON use {@link toPrettyJson}. + * + * @param value a provided value type + * @return JSON string + */ def toJson[T](value: T): String = mapper.writeValueAsString(value) + /** + * Parses given value into pretty JSON format. + * + * @param value a provided value type + * @return pretty JSON string + */ + def toPrettyJson[T](value: T): String = + mapper.writer(SerializationFeature.INDENT_OUTPUT).writeValueAsString(value) + + /** + * Parses JSON string into a type. + * + * @param jsonString a JSON string + * @return parsed value + */ + def fromJson[T: Manifest](jsonString: String): T = + mapper.readValue[T](jsonString) + + /** + * Parses JSON string into a type. + * + * @param jsonString a JSON string + * @return parsed value + */ + @deprecated("Use fromJson method instead.", "0.3.1") def parseJson[T: Manifest](jsonString: String): T = - mapper.readValue[T](jsonString, new TypeReference[T]() {}) + fromJson(jsonString) } diff --git a/src/test/scala/com/exasol/common/json/JsonMapperTest.scala b/src/test/scala/com/exasol/common/json/JsonMapperTest.scala index 2169617..57fd5ad 100644 --- a/src/test/scala/com/exasol/common/json/JsonMapperTest.scala +++ b/src/test/scala/com/exasol/common/json/JsonMapperTest.scala @@ -1,14 +1,39 @@ package com.exasol.common.json +import java.util.LinkedHashMap + +import com.fasterxml.jackson.databind.JsonNode + import org.scalatest.funsuite.AnyFunSuite class JsonMapperTest extends AnyFunSuite { - test("parse to and from json to internal data type") { + test("parses to/from JSON to internal Scala data type") { val jsonStr = """{"name":"John","age":30,"skills":["a","b","c"]}""" - val map = JsonMapper.parseJson[Map[String, Any]](jsonStr) + val map = JsonMapper.fromJson[Map[String, Any]](jsonStr) assert(map.get("skills") === Option(Seq("a", "b", "c"))) assert(JsonMapper.toJson(map) === jsonStr) } + test("parses to/from JSON to JsonNode") { + val jsonStr = + """|{ + | "number" : 1, + | "record" : { + | "field1" : "value1", + | "field2" : 23 + | } + |}""".stripMargin + val jsonNode = JsonMapper.fromJson[JsonNode](jsonStr) + assert(JsonMapper.toPrettyJson(jsonNode) === jsonStr) + } + + test("parses to/from JSON to Java LinkedHashMap") { + val jsonStr = """{"name":"John","age":"ten"}""" + val expected = new LinkedHashMap[String, String](java.util.Map.of("name", "John", "age", "ten")) + val parsedMap = JsonMapper.fromJson[LinkedHashMap[String, String]](jsonStr) + assert(parsedMap === expected) + assert(JsonMapper.toJson(parsedMap) === jsonStr) + } + }