Skip to content

Commit 08bd39a

Browse files
committed
fixes an issue with json encoding post bodies
1 parent 537f093 commit 08bd39a

2 files changed

Lines changed: 64 additions & 21 deletions

File tree

src/main/scala/com/example/scalaspringexperiment/util/CirceJsonDecoder.scala

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,66 @@
11
package com.example.scalaspringexperiment.util
22

33
import io.circe.Json
4-
import io.circe.parser.decode
4+
import io.circe.parser
55
import org.reactivestreams.Publisher
66
import org.springframework.core.ResolvableType
77
import org.springframework.core.codec.Decoder
8-
import org.springframework.core.io.buffer.DataBuffer
9-
import org.springframework.core.io.buffer.DataBufferUtils
10-
import org.springframework.http.{MediaType, ResponseEntity}
8+
import org.springframework.core.io.buffer.{DataBuffer, DataBufferUtils}
9+
import org.springframework.http.MediaType
1110
import org.springframework.util.MimeType
12-
import reactor.core.publisher.Mono
13-
import reactor.core.publisher.Flux
11+
import reactor.core.publisher.{Flux, Mono}
1412

1513
import java.nio.charset.StandardCharsets
1614
import java.util
17-
import java.lang.reflect.Type
1815

1916
class CirceJsonDecoder extends Decoder[io.circe.Json] {
2017

21-
override def canDecode(elementType: ResolvableType, mimeType: MimeType): Boolean = {
18+
override def canDecode(
19+
elementType: ResolvableType,
20+
mimeType: MimeType
21+
): Boolean = {
2222
mimeType == null || mimeType.isCompatibleWith(MediaType.APPLICATION_JSON)
2323
}
2424

25-
override def decode(input: Publisher[DataBuffer], elementType: ResolvableType, mimeType: MimeType, hints: util.Map[String, AnyRef]): Flux[io.circe.Json] = {
26-
Flux.from(input).flatMap { buffer =>
27-
val jsonStr = StandardCharsets.UTF_8.decode(buffer.asByteBuffer()).toString
28-
DataBufferUtils.release(buffer)
29-
io.circe.parser.decode[io.circe.Json](jsonStr) match {
30-
case Right(json) => Flux.just(json)
31-
case Left(err) => Flux.error(new RuntimeException(s"JSON decoding error: ${err.getMessage}"))
25+
26+
/**
27+
* TODO: there may be performance issues with how this method handles incoming JSON bodies;
28+
* particularly that it waits for the entire body to be read before decoding it.
29+
* need to investigate if this is the best approach to use with WebFlux
30+
*
31+
* @param input
32+
* @param elementType
33+
* @param mimeType
34+
* @param hints
35+
* @return
36+
*/
37+
override def decode(
38+
input: Publisher[DataBuffer],
39+
elementType: ResolvableType,
40+
mimeType: MimeType,
41+
hints: util.Map[String, AnyRef]
42+
): Flux[Json] = {
43+
DataBufferUtils.join(Flux.from(input)).flatMap { joinedBuffer =>
44+
val inputStream = joinedBuffer.asInputStream()
45+
try {
46+
val jsonStr = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8)
47+
parser.decode[Json](jsonStr) match {
48+
case Right(json) => Mono.just(json)
49+
case Left(err) => Mono.error(err)
50+
}
51+
} finally {
52+
DataBufferUtils.release(joinedBuffer)
53+
inputStream.close()
3254
}
33-
}
55+
}.flux()
3456
}
3557

36-
override def decodeToMono(input: Publisher[DataBuffer], elementType: ResolvableType, mimeType: MimeType, hints: util.Map[String, AnyRef]): Mono[io.circe.Json] = {
58+
override def decodeToMono(
59+
input: Publisher[DataBuffer],
60+
elementType: ResolvableType,
61+
mimeType: MimeType,
62+
hints: util.Map[String, AnyRef]
63+
): Mono[io.circe.Json] = {
3764
decode(input, elementType, mimeType, hints).single()
3865
}
3966

src/main/scala/com/example/scalaspringexperiment/util/CirceJsonEncoder.scala

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.example.scalaspringexperiment.util
22

3+
34
import io.circe.Json
5+
import org.reactivestreams.Publisher
46
import org.springframework.core.ResolvableType
57
import org.springframework.core.codec.Encoder
68
import org.springframework.core.io.buffer.{DataBuffer, DataBufferFactory}
@@ -10,22 +12,36 @@ import reactor.core.publisher.Flux
1012

1113
import java.nio.charset.StandardCharsets
1214
import java.util
13-
import org.reactivestreams.Publisher
1415

1516
class CirceJsonEncoder extends Encoder[io.circe.Json] {
1617

17-
override def canEncode(elementType: ResolvableType, mimeType: MimeType): Boolean =
18+
override def canEncode(
19+
elementType: ResolvableType,
20+
mimeType: MimeType
21+
): Boolean =
1822
mimeType == null || mimeType.isCompatibleWith(MediaType.APPLICATION_JSON)
1923

20-
override def encode(inputStream: Publisher[_ <: io.circe.Json], bufferFactory: DataBufferFactory, elementType: ResolvableType, mimeType: MimeType, hints: util.Map[String, AnyRef]): Flux[DataBuffer] = {
24+
override def encode(
25+
inputStream: Publisher[? <: io.circe.Json],
26+
bufferFactory: DataBufferFactory,
27+
elementType: ResolvableType,
28+
mimeType: MimeType,
29+
hints: util.Map[String, AnyRef]
30+
): Flux[DataBuffer] = {
2131
Flux.from(inputStream).map { json =>
2232
val bytes = json.noSpaces.getBytes(StandardCharsets.UTF_8)
2333
val buffer = bufferFactory.wrap(bytes)
2434
buffer
2535
}
2636
}
2737

28-
override def encodeValue(value: io.circe.Json, bufferFactory: DataBufferFactory, elementType: ResolvableType, mimeType: MimeType, hints: util.Map[String, AnyRef]): DataBuffer = {
38+
override def encodeValue(
39+
value: io.circe.Json,
40+
bufferFactory: DataBufferFactory,
41+
elementType: ResolvableType,
42+
mimeType: MimeType,
43+
hints: util.Map[String, AnyRef]
44+
): DataBuffer = {
2945
val bytes = value.noSpaces.getBytes(StandardCharsets.UTF_8)
3046
bufferFactory.wrap(bytes)
3147
}

0 commit comments

Comments
 (0)