Skip to content

Commit c0c60a6

Browse files
JVM integration with InputStream and OutputStream (#1569)
Co-authored-by: Vsevolod Tolstopyatov <qwwdfsad@gmail.com>
1 parent 0e75d25 commit c0c60a6

File tree

49 files changed

+1221
-500
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1221
-500
lines changed
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package kotlinx.benchmarks.json
2+
3+
import com.fasterxml.jackson.databind.DeserializationFeature
4+
import com.fasterxml.jackson.databind.ObjectMapper
5+
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
6+
import kotlinx.benchmarks.model.MacroTwitterFeed
7+
import kotlinx.benchmarks.model.MicroTwitterFeed
8+
import kotlinx.serialization.json.*
9+
import org.openjdk.jmh.annotations.*
10+
import java.io.*
11+
import java.nio.file.Files
12+
import java.nio.file.Path
13+
import java.util.concurrent.TimeUnit
14+
import kotlin.io.path.deleteIfExists
15+
import kotlin.io.path.outputStream
16+
17+
@Warmup(iterations = 7, time = 1)
18+
@Measurement(iterations = 7, time = 1)
19+
@BenchmarkMode(Mode.Throughput)
20+
@OutputTimeUnit(TimeUnit.SECONDS)
21+
@State(Scope.Benchmark)
22+
@Fork(2)
23+
open class TwitterFeedStreamBenchmark {
24+
val resource = TwitterFeedBenchmark::class.java.getResource("/twitter_macro.json")!!
25+
val bytes = resource.readBytes()
26+
private val twitter = Json.decodeFromString(MacroTwitterFeed.serializer(), resource.readText())
27+
28+
private val jsonIgnoreUnknwn = Json { ignoreUnknownKeys = true }
29+
private val objectMapper: ObjectMapper =
30+
jacksonObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
31+
32+
33+
private val inputStream: InputStream
34+
get() = ByteArrayInputStream(bytes)
35+
private val outputStream: OutputStream
36+
get() = ByteArrayOutputStream()
37+
38+
@Benchmark
39+
fun encodeTwitterWriteText(): OutputStream {
40+
return outputStream.use {
41+
it.bufferedWriter().write(Json.encodeToString(MacroTwitterFeed.serializer(), twitter))
42+
it
43+
}
44+
}
45+
46+
@Benchmark
47+
fun encodeTwitterWriteStream(): OutputStream {
48+
return outputStream.use {
49+
Json.encodeToStream(MacroTwitterFeed.serializer(), twitter, it)
50+
it
51+
}
52+
}
53+
54+
@Benchmark
55+
fun encodeTwitterJacksonStream(): OutputStream {
56+
return outputStream.use {
57+
objectMapper.writeValue(it, twitter)
58+
it
59+
}
60+
}
61+
62+
@Benchmark
63+
fun decodeMicroTwitterReadText(): MicroTwitterFeed {
64+
return inputStream.use {
65+
jsonIgnoreUnknwn.decodeFromString(MicroTwitterFeed.serializer(), it.bufferedReader().readText())
66+
}
67+
}
68+
69+
@Benchmark
70+
fun decodeMicroTwitterStream(): MicroTwitterFeed {
71+
return inputStream.use {
72+
jsonIgnoreUnknwn.decodeFromStream(MicroTwitterFeed.serializer(), it.buffered())
73+
}
74+
}
75+
76+
@Benchmark
77+
fun decodeMicroTwitterJacksonStream(): MicroTwitterFeed {
78+
return inputStream.use {
79+
objectMapper.readValue(it, MicroTwitterFeed::class.java)
80+
}
81+
}
82+
}

formats/json/api/kotlinx-serialization-json.api

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,3 +340,8 @@ public abstract class kotlinx/serialization/json/JsonTransformingSerializer : ko
340340
protected fun transformSerialize (Lkotlinx/serialization/json/JsonElement;)Lkotlinx/serialization/json/JsonElement;
341341
}
342342

343+
public final class kotlinx/serialization/json/JvmStreamsKt {
344+
public static final fun decodeFromStream (Lkotlinx/serialization/json/Json;Lkotlinx/serialization/DeserializationStrategy;Ljava/io/InputStream;)Ljava/lang/Object;
345+
public static final fun encodeToStream (Lkotlinx/serialization/json/Json;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Ljava/io/OutputStream;)V
346+
}
347+

formats/json/commonMain/src/kotlinx/serialization/json/Json.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public sealed class Json(
9595
* @throws [SerializationException] if the given JSON string cannot be deserialized to the value of type [T].
9696
*/
9797
public final override fun <T> decodeFromString(deserializer: DeserializationStrategy<T>, string: String): T {
98-
val lexer = JsonLexer(string)
98+
val lexer = StringJsonLexer(string)
9999
val input = StreamingJsonDecoder(this, WriteMode.OBJ, lexer, deserializer.descriptor)
100100
val result = input.decodeSerializableValue(deserializer)
101101
lexer.expectEof()

formats/json/commonMain/src/kotlinx/serialization/json/internal/Composers.kt

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,39 +2,32 @@
22
* Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

5+
@file:OptIn(ExperimentalSerializationApi::class)
56
package kotlinx.serialization.json.internal
67

7-
import kotlinx.serialization.*
8-
import kotlinx.serialization.json.*
9-
import kotlin.jvm.*
8+
import kotlinx.serialization.ExperimentalSerializationApi
9+
import kotlinx.serialization.json.Json
10+
import kotlin.jvm.JvmField
11+
12+
internal fun Composer(sb: JsonStringBuilder, json: Json): Composer =
13+
if (json.configuration.prettyPrint) ComposerWithPrettyPrint(sb, json) else Composer(sb)
1014

1115
@OptIn(ExperimentalSerializationApi::class)
12-
internal open class Composer(@JvmField internal val sb: JsonStringBuilder, @JvmField internal val json: Json) {
13-
private var level = 0
16+
internal open class Composer(@JvmField internal val sb: JsonStringBuilder) {
1417
var writingFirst = true
15-
private set
18+
protected set
1619

17-
fun indent() {
20+
open fun indent() {
1821
writingFirst = true
19-
level++
2022
}
2123

22-
fun unIndent() {
23-
level--
24-
}
24+
open fun unIndent() = Unit
2525

26-
fun nextItem() {
26+
open fun nextItem() {
2727
writingFirst = false
28-
if (json.configuration.prettyPrint) {
29-
print("\n")
30-
repeat(level) { print(json.configuration.prettyPrintIndent) }
31-
}
3228
}
3329

34-
fun space() {
35-
if (json.configuration.prettyPrint)
36-
print(' ')
37-
}
30+
open fun space() = Unit
3831

3932
fun print(v: Char) = sb.append(v)
4033
fun print(v: String) = sb.append(v)
@@ -49,7 +42,7 @@ internal open class Composer(@JvmField internal val sb: JsonStringBuilder, @JvmF
4942
}
5043

5144
@ExperimentalUnsignedTypes
52-
internal class ComposerForUnsignedNumbers(sb: JsonStringBuilder, json: Json) : Composer(sb, json) {
45+
internal class ComposerForUnsignedNumbers(sb: JsonStringBuilder) : Composer(sb) {
5346
override fun print(v: Int) {
5447
return super.print(v.toUInt().toString())
5548
}
@@ -66,3 +59,29 @@ internal class ComposerForUnsignedNumbers(sb: JsonStringBuilder, json: Json) : C
6659
return super.print(v.toUShort().toString())
6760
}
6861
}
62+
63+
internal class ComposerWithPrettyPrint(
64+
sb: JsonStringBuilder,
65+
private val json: Json
66+
) : Composer(sb) {
67+
private var level = 0
68+
69+
override fun indent() {
70+
writingFirst = true
71+
level++
72+
}
73+
74+
override fun unIndent() {
75+
level--
76+
}
77+
78+
override fun nextItem() {
79+
writingFirst = false
80+
print("\n")
81+
repeat(level) { print(json.configuration.prettyPrintIndent) }
82+
}
83+
84+
override fun space() {
85+
print(' ')
86+
}
87+
}

formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonExceptions.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ internal fun JsonDecodingException(offset: Int, message: String) =
2828
*/
2929
internal class JsonEncodingException(message: String) : JsonException(message)
3030

31-
internal fun JsonDecodingException(offset: Int, message: String, input: String) =
31+
internal fun JsonDecodingException(offset: Int, message: String, input: CharSequence) =
3232
JsonDecodingException(offset, "$message\nJSON input: ${input.minify(offset)}")
3333

3434
internal fun InvalidFloatingPointEncoded(value: Number, output: String) = JsonEncodingException(
@@ -45,7 +45,7 @@ internal fun InvalidFloatingPointDecoded(value: Number, key: String, output: Str
4545
JsonDecodingException(-1, unexpectedFpErrorMessage(value, key, output))
4646

4747
// Extension on JSON reader and fail immediately
48-
internal fun JsonLexer.throwInvalidFloatingPointDecoded(result: Number): Nothing {
48+
internal fun AbstractJsonLexer.throwInvalidFloatingPointDecoded(result: Number): Nothing {
4949
fail("Unexpected special floating-point value $result. By default, " +
5050
"non-finite floating point values are prohibited because they do not conform JSON specification. " +
5151
specialFlowingValuesHint
@@ -73,7 +73,7 @@ internal fun InvalidKeyKindException(keyDescriptor: SerialDescriptor) = JsonEnco
7373
allowStructuredMapKeysHint
7474
)
7575

76-
private fun String.minify(offset: Int = -1): String {
76+
private fun CharSequence.minify(offset: Int = -1): CharSequence {
7777
if (length < 200) return this
7878
if (offset == -1) {
7979
val start = this.length - 60

formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonTreeReader.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import kotlinx.serialization.json.*
1010
@OptIn(ExperimentalSerializationApi::class)
1111
internal class JsonTreeReader(
1212
configuration: JsonConfiguration,
13-
private val lexer: JsonLexer
13+
private val lexer: AbstractJsonLexer
1414
) {
1515
private val isLenient = configuration.isLenient
1616
private var stackDepth = 0

formats/json/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonDecoder.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ import kotlinx.serialization.modules.*
1313
import kotlin.jvm.*
1414

1515
/**
16-
* [JsonDecoder] which reads given JSON from [JsonLexer] field by field.
16+
* [JsonDecoder] which reads given JSON from [AbstractJsonLexer] field by field.
1717
*/
1818
@OptIn(ExperimentalSerializationApi::class, ExperimentalUnsignedTypes::class)
1919
internal open class StreamingJsonDecoder(
2020
final override val json: Json,
2121
private val mode: WriteMode,
22-
@JvmField internal val lexer: JsonLexer,
22+
@JvmField internal val lexer: AbstractJsonLexer,
2323
descriptor: SerialDescriptor
2424
) : JsonDecoder, AbstractDecoder() {
2525

@@ -256,7 +256,7 @@ internal open class StreamingJsonDecoder(
256256
@OptIn(ExperimentalSerializationApi::class)
257257
@ExperimentalUnsignedTypes
258258
internal class JsonDecoderForUnsignedTypes(
259-
private val lexer: JsonLexer,
259+
private val lexer: AbstractJsonLexer,
260260
json: Json
261261
) : AbstractDecoder() {
262262
override val serializersModule: SerializersModule = json.serializersModule
@@ -268,7 +268,7 @@ internal class JsonDecoderForUnsignedTypes(
268268
override fun decodeShort(): Short = lexer.parseString("UShort") { toUShort().toShort() }
269269
}
270270

271-
private inline fun <T> JsonLexer.parseString(expectedType: String, block: String.() -> T): T {
271+
private inline fun <T> AbstractJsonLexer.parseString(expectedType: String, block: String.() -> T): T {
272272
val input = consumeStringLenient()
273273
try {
274274
return input.block()

formats/json/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonEncoder.kt

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -160,10 +160,7 @@ internal class StreamingJsonEncoder(
160160

161161
override fun encodeInline(inlineDescriptor: SerialDescriptor): Encoder =
162162
if (inlineDescriptor.isUnsignedNumber) StreamingJsonEncoder(
163-
ComposerForUnsignedNumbers(
164-
composer.sb,
165-
json
166-
), json, mode, null
163+
ComposerForUnsignedNumbers(composer.sb), json, mode, null
167164
)
168165
else super.encodeInline(inlineDescriptor)
169166

formats/json/commonMain/src/kotlinx/serialization/json/internal/TreeJsonDecoder.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ private sealed class AbstractJsonTreeDecoder(
163163

164164
@OptIn(ExperimentalUnsignedTypes::class)
165165
override fun decodeTaggedInline(tag: String, inlineDescriptor: SerialDescriptor): Decoder =
166-
if (inlineDescriptor.isUnsignedNumber) JsonDecoderForUnsignedTypes(JsonLexer(getPrimitiveValue(tag).content), json)
166+
if (inlineDescriptor.isUnsignedNumber) JsonDecoderForUnsignedTypes(StringJsonLexer(getPrimitiveValue(tag).content), json)
167167
else super.decodeTaggedInline(tag, inlineDescriptor)
168168
}
169169

0 commit comments

Comments
 (0)