Skip to content

Commit 9eb7288

Browse files
committed
🧹 chore: upgrade to Jackson 3
1 parent f240b90 commit 9eb7288

7 files changed

Lines changed: 120 additions & 120 deletions

File tree

‎build.sbt‎

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,12 @@ def specs2(scalaVersion: String) =
2121
("org.specs2" %% s"specs2-$n" % "4.23.0") % Test
2222
}
2323

24-
val jacksonDatabindVersion = "2.20.1"
25-
val jacksonDatabind = Seq(
26-
"com.fasterxml.jackson.core" % "jackson-databind" % jacksonDatabindVersion
27-
)
28-
29-
val jacksonVersion = jacksonDatabindVersion
24+
val jacksonVersion = "3.0.3"
3025
val jacksons = Seq(
31-
"com.fasterxml.jackson.core" % "jackson-core",
32-
"com.fasterxml.jackson.datatype" % "jackson-datatype-jdk8",
33-
"com.fasterxml.jackson.datatype" % "jackson-datatype-jsr310",
34-
"com.fasterxml.jackson.module" % "jackson-module-parameter-names",
35-
"com.fasterxml.jackson.module" %% "jackson-module-scala",
36-
).map(_ % jacksonVersion) ++ jacksonDatabind
26+
"tools.jackson.core" % "jackson-core",
27+
"tools.jackson.core" % "jackson-databind",
28+
"tools.jackson.module" %% "jackson-module-scala",
29+
).map(_ % jacksonVersion)
3730

3831
val joda = Seq(
3932
"joda-time" % "joda-time" % "2.14.0"

‎play-json/jvm/src/main/scala/play/api/libs/json/EnvReads.scala‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ import java.util.Locale
2626

2727
import scala.util.control.NonFatal
2828

29-
import com.fasterxml.jackson.databind.JsonNode
30-
import com.fasterxml.jackson.databind.node.ArrayNode
31-
import com.fasterxml.jackson.databind.node.ObjectNode
29+
import tools.jackson.databind.JsonNode
30+
import tools.jackson.databind.node.ArrayNode
31+
import tools.jackson.databind.node.ObjectNode
3232

3333
import play.api.libs.json.jackson.JacksonJson
3434

‎play-json/jvm/src/main/scala/play/api/libs/json/EnvWrites.scala‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import java.time.ZonedDateTime
1818
import java.time.{ Duration => JDuration }
1919
import java.util.Locale
2020

21-
import com.fasterxml.jackson.databind.JsonNode
21+
import tools.jackson.databind.JsonNode
2222
import play.api.libs.json.jackson.JacksonJson
2323

2424
trait EnvWrites {

‎play-json/jvm/src/main/scala/play/api/libs/json/JsonConfig.scala‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
package play.api.libs.json
66

7-
import com.fasterxml.jackson.core.StreamReadConstraints
8-
import com.fasterxml.jackson.core.StreamWriteConstraints
7+
import tools.jackson.core.StreamReadConstraints
8+
import tools.jackson.core.StreamWriteConstraints
99

1010
import play.api.libs.json.JsonConfig.defaultMaxPlain
1111
import play.api.libs.json.JsonConfig.defaultMinPlain

‎play-json/jvm/src/main/scala/play/api/libs/json/jackson/JacksonJson.scala‎

Lines changed: 76 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -14,26 +14,19 @@ import scala.collection.mutable
1414
import scala.collection.mutable.ArrayBuffer
1515
import scala.collection.mutable.ListBuffer
1616

17-
import com.fasterxml.jackson.core.JsonFactoryBuilder
18-
import com.fasterxml.jackson.core.JsonGenerator
19-
import com.fasterxml.jackson.core.JsonParser
20-
import com.fasterxml.jackson.core.JsonTokenId
21-
import com.fasterxml.jackson.core.Version
22-
import com.fasterxml.jackson.core.json.JsonWriteFeature
23-
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter
24-
25-
import com.fasterxml.jackson.databind.Module.SetupContext
26-
import com.fasterxml.jackson.databind._
27-
import com.fasterxml.jackson.databind.`type`.TypeFactory
28-
import com.fasterxml.jackson.databind.deser.Deserializers
29-
import com.fasterxml.jackson.databind.json.JsonMapper
30-
import com.fasterxml.jackson.databind.module.SimpleModule
31-
import com.fasterxml.jackson.databind.ser.Serializers
32-
import com.fasterxml.jackson.databind.util.TokenBuffer
33-
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module
34-
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
35-
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule
36-
import com.fasterxml.jackson.module.scala.DefaultScalaModule
17+
import com.fasterxml.jackson.annotation.JsonFormat
18+
import tools.jackson.core.{ JsonGenerator, JsonParser, JsonTokenId, Version }
19+
import tools.jackson.core.json.{ JsonFactory, JsonWriteFeature }
20+
import tools.jackson.databind.JacksonModule.SetupContext
21+
import tools.jackson.databind._
22+
import tools.jackson.databind.`type`.TypeFactory
23+
import tools.jackson.databind.cfg.DateTimeFeature
24+
import tools.jackson.databind.deser.Deserializers
25+
import tools.jackson.databind.json.JsonMapper
26+
import tools.jackson.databind.module.SimpleModule
27+
import tools.jackson.databind.ser.Serializers
28+
import tools.jackson.databind.util.TokenBuffer
29+
import tools.jackson.module.scala.DefaultScalaModule
3730

3831
import play.api.libs.json._
3932

@@ -66,7 +59,7 @@ sealed class PlayJsonMapperModule(jsonConfig: JsonConfig) extends SimpleModule("
6659

6760
// -- Serializers.
6861

69-
private[jackson] class JsValueSerializer(jsonConfig: JsonConfig) extends JsonSerializer[JsValue] {
62+
private[jackson] class JsValueSerializer(jsonConfig: JsonConfig) extends ValueSerializer[JsValue] {
7063
import java.math.{ BigDecimal => JBigDec }
7164

7265
private def stripTrailingZeros(bigDec: JBigDec): JBigDec = {
@@ -79,7 +72,7 @@ private[jackson] class JsValueSerializer(jsonConfig: JsonConfig) extends JsonSer
7972
}
8073
}
8174

82-
override def serialize(value: JsValue, json: JsonGenerator, provider: SerializerProvider): Unit = {
75+
override def serialize(value: JsValue, json: JsonGenerator, ctxt: SerializationContext): Unit = {
8376
value match {
8477
case JsNumber(v) => {
8578
// Workaround #3784: Same behaviour as if JsonGenerator were
@@ -89,18 +82,22 @@ private[jackson] class JsValueSerializer(jsonConfig: JsonConfig) extends JsonSer
8982
val va = v.abs
9083
va <= jsonConfig.bigDecimalSerializerConfig.maxPlain && va >= jsonConfig.bigDecimalSerializerConfig.minPlain
9184
}
92-
val stripped = stripTrailingZeros(v.bigDecimal)
93-
val raw = if (shouldWritePlain) stripped.toPlainString else stripped.toString
94-
95-
if (raw.exists(c => c == 'E' || c == '.'))
96-
json.writeNumber(raw)
97-
else
98-
json match {
99-
case tb: TokenBuffer =>
100-
tb.writeNumber(raw, true)
101-
case _ =>
102-
json.writeNumber(raw)
103-
}
85+
val stripped = stripTrailingZeros(v.bigDecimal)
86+
val (raw, rawAsBigDecimal) = if (shouldWritePlain) {
87+
val str = stripped.toPlainString
88+
(str, new JBigDec(str))
89+
} else {
90+
(stripped.toString, stripped)
91+
}
92+
93+
json match {
94+
case tb: TokenBuffer =>
95+
// If the JsonGenerator is a TokenBuffer, use its writeNumber specific method
96+
val isInteger = raw.forall(c => c != 'E' && c != '.')
97+
tb.writeNumber(raw, isInteger)
98+
case _ =>
99+
json.writeNumber(rawAsBigDecimal)
100+
}
104101
}
105102

106103
case JsString(v) => json.writeString(v)
@@ -109,16 +106,16 @@ private[jackson] class JsValueSerializer(jsonConfig: JsonConfig) extends JsonSer
109106
case JsArray(elements) => {
110107
json.writeStartArray()
111108
elements.foreach { t =>
112-
serialize(t, json, provider)
109+
serialize(t, json, ctxt)
113110
}
114111
json.writeEndArray()
115112
}
116113

117114
case JsObject(values) => {
118115
json.writeStartObject()
119116
values.foreach { t =>
120-
json.writeFieldName(t._1)
121-
serialize(t._2, json, provider)
117+
json.writeName(t._1)
118+
serialize(t._2, json, ctxt)
122119
}
123120
json.writeEndObject()
124121
}
@@ -152,7 +149,7 @@ private[jackson] case class ReadingMap(content: ListBuffer[(String, JsValue)]) e
152149
}
153150

154151
private[jackson] class JsValueDeserializer(factory: TypeFactory, klass: Class[?], jsonConfig: JsonConfig)
155-
extends JsonDeserializer[Object] {
152+
extends ValueDeserializer[Object] {
156153
override def isCachable: Boolean = true
157154

158155
override def deserialize(jp: JsonParser, ctxt: DeserializationContext): JsValue = {
@@ -168,7 +165,7 @@ private[jackson] class JsValueDeserializer(factory: TypeFactory, klass: Class[?]
168165
jp: JsonParser,
169166
parserContext: List[DeserializerContext]
170167
): (Some[JsNumber], List[DeserializerContext]) = {
171-
BigDecimalParser.parse(jp.getText, jsonConfig) match {
168+
BigDecimalParser.parse(jp.getString, jsonConfig) match {
172169
case JsSuccess(bigDecimal, _) =>
173170
(Some(JsNumber(bigDecimal)), parserContext)
174171

@@ -193,14 +190,14 @@ private[jackson] class JsValueDeserializer(factory: TypeFactory, klass: Class[?]
193190
ctxt: DeserializationContext,
194191
parserContext: List[DeserializerContext]
195192
): JsValue = {
196-
if (jp.getCurrentToken == null) {
193+
if (jp.currentToken() == null) {
197194
jp.nextToken() // happens when using treeToValue (we're not parsing tokens)
198195
}
199196

200-
val valueAndCtx = (jp.getCurrentToken.id(): @switch) match {
197+
val valueAndCtx = (jp.currentToken().id(): @switch) match {
201198
case JsonTokenId.ID_NUMBER_INT | JsonTokenId.ID_NUMBER_FLOAT => parseBigDecimal(jp, parserContext)
202199

203-
case JsonTokenId.ID_STRING => (Some(JsString(jp.getText)), parserContext)
200+
case JsonTokenId.ID_STRING => (Some(JsString(jp.getString)), parserContext)
204201

205202
case JsonTokenId.ID_TRUE => (Some(JsBoolean(true)), parserContext)
206203

@@ -218,7 +215,7 @@ private[jackson] class JsValueDeserializer(factory: TypeFactory, klass: Class[?]
218215

219216
case JsonTokenId.ID_START_OBJECT => (None, ReadingMap(ListBuffer()) +: parserContext)
220217

221-
case JsonTokenId.ID_FIELD_NAME =>
218+
case JsonTokenId.ID_PROPERTY_NAME =>
222219
parserContext match {
223220
case (c: ReadingMap) :: stack => (None, c.setField(jp.currentName()) +: stack)
224221
case _ => throw new RuntimeException("We should be reading map, something got wrong")
@@ -248,26 +245,39 @@ private[jackson] class JsValueDeserializer(factory: TypeFactory, klass: Class[?]
248245
}
249246

250247
// This is used when the root object is null, ie when deserializing "null"
251-
override val getNullValue = JsNull
248+
override def getNullValue(ctxt: DeserializationContext) = JsNull
252249
}
253250

254251
private[jackson] class PlayDeserializers(jsonSettings: JsonConfig) extends Deserializers.Base {
255-
override def findBeanDeserializer(javaType: JavaType, config: DeserializationConfig, beanDesc: BeanDescription) = {
252+
override def findBeanDeserializer(
253+
javaType: JavaType,
254+
config: DeserializationConfig,
255+
beanDescRef: BeanDescription.Supplier
256+
) = {
256257
val klass = javaType.getRawClass
257258
if (classOf[JsValue].isAssignableFrom(klass) || klass == JsNull.getClass) {
258259
new JsValueDeserializer(config.getTypeFactory, klass, jsonSettings)
259260
} else null
260261
}
262+
263+
override def hasDeserializerFor(config: DeserializationConfig, valueType: Class[?]): Boolean = {
264+
classOf[JsValue].isAssignableFrom(valueType) || valueType == JsNull.getClass
265+
}
261266
}
262267

263268
private[jackson] class PlaySerializers(jsonSettings: JsonConfig) extends Serializers.Base {
264-
override def findSerializer(config: SerializationConfig, javaType: JavaType, beanDesc: BeanDescription) = {
265-
val ser: Object = if (classOf[JsValue].isAssignableFrom(beanDesc.getBeanClass)) {
269+
override def findSerializer(
270+
config: SerializationConfig,
271+
javaType: JavaType,
272+
beanDescRef: BeanDescription.Supplier,
273+
formatOverrides: JsonFormat.Value
274+
) = {
275+
val ser: Object = if (classOf[JsValue].isAssignableFrom(beanDescRef.getBeanClass)) {
266276
new JsValueSerializer(jsonSettings)
267277
} else {
268278
null
269279
}
270-
ser.asInstanceOf[JsonSerializer[Object]]
280+
ser.asInstanceOf[ValueSerializer[Object]]
271281
}
272282
}
273283

@@ -283,51 +293,43 @@ private[play] object JacksonJson {
283293
}
284294

285295
private[play] case class JacksonJson(defaultMapperJsonConfig: JsonConfig) {
286-
private var currentMapper: ObjectMapper = null
287-
private val defaultMapper: ObjectMapper = JsonMapper
296+
private var currentMapper: JsonMapper = null
297+
private val defaultMapper: JsonMapper = JsonMapper
288298
.builder(
289-
new JsonFactoryBuilder()
299+
JsonFactory
300+
.builder()
290301
.streamReadConstraints(defaultMapperJsonConfig.streamReadConstraints)
291302
.streamWriteConstraints(defaultMapperJsonConfig.streamWriteConstraints)
292303
.build()
293304
)
294305
.addModules(
295-
new ParameterNamesModule(),
296-
new Jdk8Module(),
297-
new JavaTimeModule(),
298306
new DefaultScalaModule(),
299307
new PlayJsonMapperModule(defaultMapperJsonConfig),
300308
)
301309
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
302-
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
303-
.disable(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS)
310+
.disable(DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS)
311+
.disable(DateTimeFeature.WRITE_DURATIONS_AS_TIMESTAMPS)
304312
.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
305313
.build()
306314

307-
private[play] def mapper(): ObjectMapper = if (currentMapper == null) {
315+
private[play] def mapper(): JsonMapper = if (currentMapper == null) {
308316
defaultMapper
309317
} else {
310318
currentMapper
311319
}
312320

313-
private[play] def setObjectMapper(mapper: ObjectMapper): Unit = {
321+
private[play] def setObjectMapper(mapper: JsonMapper): Unit = {
314322
this.currentMapper = mapper
315323
}
316324

317-
private def stringJsonGenerator(out: StringWriter) =
318-
mapper().getFactory.createGenerator(out)
319-
320-
private def stringJsonGenerator(out: OutputStream) =
321-
mapper().getFactory.createGenerator(out)
322-
323325
def parseJsValue(data: Array[Byte]): JsValue =
324-
mapper().readValue(mapper().getFactory.createParser(data), classOf[JsValue])
326+
mapper().readValue(mapper().createParser(data), classOf[JsValue])
325327

326328
def parseJsValue(input: String): JsValue =
327-
mapper().readValue(mapper().getFactory.createParser(input), classOf[JsValue])
329+
mapper().readValue(mapper().createParser(input), classOf[JsValue])
328330

329331
def parseJsValue(stream: InputStream): JsValue =
330-
mapper().readValue(mapper().getFactory.createParser(stream), classOf[JsValue])
332+
mapper().readValue(mapper().createParser(stream), classOf[JsValue])
331333

332334
private def withStringWriter[T](f: StringWriter => T): T = {
333335
val sw = new StringWriter()
@@ -347,35 +349,29 @@ private[play] case class JacksonJson(defaultMapperJsonConfig: JsonConfig) {
347349

348350
def generateFromJsValue(jsValue: JsValue, escapeNonASCII: Boolean): String =
349351
withStringWriter { sw =>
350-
val gen = stringJsonGenerator(sw)
351-
352-
if (escapeNonASCII) {
353-
gen.enable(JsonWriteFeature.ESCAPE_NON_ASCII.mappedFeature)
352+
val mapperWithEscapeNonASCII = if (escapeNonASCII) {
353+
mapper().rebuild().enable(JsonWriteFeature.ESCAPE_NON_ASCII).build()
354+
} else {
355+
mapper()
354356
}
355357

356-
mapper().writeValue(gen, jsValue)
358+
mapperWithEscapeNonASCII.writeValue(sw, jsValue)
357359
sw.flush()
358360
sw.getBuffer.toString
359361
}
360362

361363
def prettyPrint(jsValue: JsValue): String = withStringWriter { sw =>
362-
val gen = stringJsonGenerator(sw).setPrettyPrinter(
363-
new DefaultPrettyPrinter()
364-
)
365364
val writer: ObjectWriter = mapper().writerWithDefaultPrettyPrinter()
366365

367-
writer.writeValue(gen, jsValue)
366+
writer.writeValue(sw, jsValue)
368367
sw.flush()
369368
sw.getBuffer.toString
370369
}
371370

372371
def prettyPrintToStream(jsValue: JsValue, stream: OutputStream): Unit = {
373-
val gen = stringJsonGenerator(stream).setPrettyPrinter(
374-
new DefaultPrettyPrinter()
375-
)
376372
val writer: ObjectWriter = mapper().writerWithDefaultPrettyPrinter()
377373

378-
writer.writeValue(gen, jsValue)
374+
writer.writeValue(stream, jsValue)
379375
}
380376

381377
def jsValueToBytes(jsValue: JsValue): Array[Byte] =

‎play-json/jvm/src/test/scala/play/api/libs/json/JsonConfigSpec.scala‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
package play.api.libs.json
66

7-
import com.fasterxml.jackson.core.{ StreamReadConstraints, StreamWriteConstraints }
7+
import tools.jackson.core.{ StreamReadConstraints, StreamWriteConstraints }
88
import org.scalatest.matchers.must.Matchers
99
import org.scalatest.wordspec.AnyWordSpec
1010

0 commit comments

Comments
 (0)