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
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package play.api.libs.json.jackson

import java.io.InputStream
import java.io.OutputStream
import java.io.StringWriter

import scala.annotation.switch
import scala.annotation.tailrec
Expand All @@ -20,7 +19,6 @@ import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.core.JsonTokenId
import com.fasterxml.jackson.core.Version
import com.fasterxml.jackson.core.json.JsonWriteFeature
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter

import com.fasterxml.jackson.databind.Module.SetupContext
import com.fasterxml.jackson.databind._
Expand Down Expand Up @@ -283,43 +281,56 @@ private[play] object JacksonJson {
}

private[play] case class JacksonJson(defaultMapperJsonConfig: JsonConfig) {
private var currentMapper: ObjectMapper = null
private val defaultMapper: ObjectMapper = JsonMapper
.builder(
new JsonFactoryBuilder()
.streamReadConstraints(defaultMapperJsonConfig.streamReadConstraints)
.streamWriteConstraints(defaultMapperJsonConfig.streamWriteConstraints)
.build()
)
.addModules(
new ParameterNamesModule(),
new Jdk8Module(),
new JavaTimeModule(),
new DefaultScalaModule(),
new PlayJsonMapperModule(defaultMapperJsonConfig),
)
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.disable(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS)
.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
.build()

private[play] def mapper(): ObjectMapper = if (currentMapper == null) {
defaultMapper
} else {
currentMapper
private var currentMapper: ObjectMapper = null
private var currentMapperWithEscapeNonAscii: ObjectMapper = null

private val defaultMapper: ObjectMapper = buildMapper(false)
private val defaultMapperWithEscapeNonAscii: ObjectMapper = buildMapper(true)

private def buildMapper(escapeNonAscii: Boolean): ObjectMapper = {
JsonMapper
.builder(
new JsonFactoryBuilder()
.streamReadConstraints(defaultMapperJsonConfig.streamReadConstraints)
.streamWriteConstraints(defaultMapperJsonConfig.streamWriteConstraints)
.build()
)
.addModules(
new ParameterNamesModule(),
new Jdk8Module(),
new JavaTimeModule(),
new DefaultScalaModule(),
new PlayJsonMapperModule(defaultMapperJsonConfig),
)
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.disable(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS)
.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
.configure(JsonWriteFeature.ESCAPE_NON_ASCII, escapeNonAscii)
.build()
}

private[play] def mapper(escapeNonAscii: Boolean = false): ObjectMapper = {
if (escapeNonAscii) {
if (currentMapperWithEscapeNonAscii == null) {
defaultMapperWithEscapeNonAscii
} else {
currentMapperWithEscapeNonAscii
}
} else {
if (currentMapper == null) {
defaultMapper
} else {
currentMapper
}
}
}

private[play] def setObjectMapper(mapper: ObjectMapper): Unit = {
this.currentMapper = mapper
this.currentMapperWithEscapeNonAscii = mapper.copy().enable(JsonWriteFeature.ESCAPE_NON_ASCII.mappedFeature)
}

private def stringJsonGenerator(out: StringWriter) =
mapper().getFactory.createGenerator(out)

private def stringJsonGenerator(out: OutputStream) =
mapper().getFactory.createGenerator(out)

def parseJsValue(data: Array[Byte]): JsValue =
mapper().readValue(mapper().getFactory.createParser(data), classOf[JsValue])

Expand All @@ -329,53 +340,17 @@ private[play] case class JacksonJson(defaultMapperJsonConfig: JsonConfig) {
def parseJsValue(stream: InputStream): JsValue =
mapper().readValue(mapper().getFactory.createParser(stream), classOf[JsValue])

private def withStringWriter[T](f: StringWriter => T): T = {
val sw = new StringWriter()

try {
f(sw)
} catch {
case err: Throwable => throw err
} finally {
if (sw != null) try {
sw.close()
} catch {
case _: Throwable => ()
}
}
}

def generateFromJsValue(jsValue: JsValue, escapeNonASCII: Boolean): String =
withStringWriter { sw =>
val gen = stringJsonGenerator(sw)

if (escapeNonASCII) {
gen.enable(JsonWriteFeature.ESCAPE_NON_ASCII.mappedFeature)
}
mapper(escapeNonASCII).writeValueAsString(jsValue)

mapper().writeValue(gen, jsValue)
sw.flush()
sw.getBuffer.toString
}

def prettyPrint(jsValue: JsValue): String = withStringWriter { sw =>
val gen = stringJsonGenerator(sw).setPrettyPrinter(
new DefaultPrettyPrinter()
)
def prettyPrint(jsValue: JsValue): String = {
val writer: ObjectWriter = mapper().writerWithDefaultPrettyPrinter()

writer.writeValue(gen, jsValue)
sw.flush()
sw.getBuffer.toString
writer.writeValueAsString(jsValue)
}

def prettyPrintToStream(jsValue: JsValue, stream: OutputStream): Unit = {
val gen = stringJsonGenerator(stream).setPrettyPrinter(
new DefaultPrettyPrinter()
)
val writer: ObjectWriter = mapper().writerWithDefaultPrettyPrinter()

writer.writeValue(gen, jsValue)
writer.writeValue(stream, jsValue)
}

def jsValueToBytes(jsValue: JsValue): Array[Byte] =
Expand Down
36 changes: 26 additions & 10 deletions play-json/jvm/src/test/scala/play/api/libs/json/JsonSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import java.util.TimeZone

import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.node.{ ArrayNode, NumericNode, ObjectNode }
import play.api.libs.functional.syntax._
import play.api.libs.json.Json._
import play.api.libs.json.jackson.JacksonJson
Expand Down Expand Up @@ -469,36 +470,51 @@ class JsonSpec extends org.specs2.mutable.Specification {
}

"Serialize and deserialize Jackson ObjectNodes" in {
val on = mapper
val on: ObjectNode = mapper
.createObjectNode()
.put("foo", 1)
.put("bar", "two")
val json = Json.obj("foo" -> 1, "bar" -> "two")
val json = Json.obj("foo" -> 1, "bar" -> "two")
val deserialized: JsResult[JsonNode] = fromJson[JsonNode](json)

toJson(on).must_==(json) and (
fromJson[JsonNode](json).map(_.toString).must_==(JsSuccess(on.toString))
deserialized.map(_.isInstanceOf[ObjectNode]).must_==(JsSuccess(true))
) and (
deserialized.map(_.toString).must_==(JsSuccess(on.toString))
)
}

"Serialize and deserialize Jackson ArrayNodes" in {
val an = mapper
val an: ArrayNode = mapper
.createArrayNode()
.add("one")
.add(2)
val json = Json.arr("one", 2)
val json = Json.arr("one", 2)
val deserialized: JsResult[JsonNode] = fromJson[JsonNode](json)

toJson(an).must(equalTo(json)) and (
fromJson[JsonNode](json).map(_.toString).must_==(JsSuccess(an.toString))
deserialized.map(_.isInstanceOf[ArrayNode]).must_==(JsSuccess(true))
) and (
deserialized.map(_.toString).must_==(JsSuccess(an.toString))
)
}

"Deserialize integer JsNumber as Jackson number node" in {
val jsNum = JsNumber(new java.math.BigDecimal("50"))
fromJson[JsonNode](jsNum).map(_.toString).must_==(JsSuccess("50"))
val jsNum = JsNumber(new java.math.BigDecimal("50"))
val deserialized: JsResult[JsonNode] = fromJson[JsonNode](jsNum)

deserialized.map(_.isInstanceOf[NumericNode]).must_==(JsSuccess(true)) and (
deserialized.map(_.toString).must_==(JsSuccess("50"))
)
}

"Deserialize float JsNumber as Jackson number node" in {
val jsNum = JsNumber(new java.math.BigDecimal("12.345"))
fromJson[JsonNode](jsNum).map(_.toString).must_==(JsSuccess("12.345"))
val jsNum = JsNumber(new java.math.BigDecimal("12.345"))
val deserialized: JsResult[JsonNode] = fromJson[JsonNode](jsNum)

deserialized.map(_.isInstanceOf[NumericNode]).must_==(JsSuccess(true)) and (
deserialized.map(_.toString).must_==(JsSuccess("12.345"))
)
}

"Serialize JsNumbers with integers correctly" in {
Expand Down
Loading