Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JVM integration with InputStream and OutputStream #1569

Merged
merged 7 commits into from
Sep 3, 2021
Merged
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
@@ -0,0 +1,82 @@
package kotlinx.benchmarks.json

import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import kotlinx.benchmarks.model.MacroTwitterFeed
import kotlinx.benchmarks.model.MicroTwitterFeed
import kotlinx.serialization.json.*
import org.openjdk.jmh.annotations.*
import java.io.*
import java.nio.file.Files
import java.nio.file.Path
import java.util.concurrent.TimeUnit
import kotlin.io.path.deleteIfExists
import kotlin.io.path.outputStream

@Warmup(iterations = 7, time = 1)
@Measurement(iterations = 7, time = 1)
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
@State(Scope.Benchmark)
@Fork(2)
open class TwitterFeedStreamBenchmark {
qwwdfsad marked this conversation as resolved.
Show resolved Hide resolved
val resource = TwitterFeedBenchmark::class.java.getResource("/twitter_macro.json")!!
val bytes = resource.readBytes()
private val twitter = Json.decodeFromString(MacroTwitterFeed.serializer(), resource.readText())

private val jsonIgnoreUnknwn = Json { ignoreUnknownKeys = true }
private val objectMapper: ObjectMapper =
jacksonObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)


private val inputStream: InputStream
get() = ByteArrayInputStream(bytes)
private val outputStream: OutputStream
get() = ByteArrayOutputStream()

@Benchmark
fun encodeTwitterWriteText(): OutputStream {
return outputStream.use {
it.bufferedWriter().write(Json.encodeToString(MacroTwitterFeed.serializer(), twitter))
it
}
}

@Benchmark
fun encodeTwitterWriteStream(): OutputStream {
return outputStream.use {
Json.encodeToStream(MacroTwitterFeed.serializer(), twitter, it)
it
}
}

@Benchmark
fun encodeTwitterJacksonStream(): OutputStream {
return outputStream.use {
objectMapper.writeValue(it, twitter)
it
}
}

@Benchmark
fun decodeMicroTwitterReadText(): MicroTwitterFeed {
return inputStream.use {
jsonIgnoreUnknwn.decodeFromString(MicroTwitterFeed.serializer(), it.bufferedReader().readText())
}
}

@Benchmark
fun decodeMicroTwitterStream(): MicroTwitterFeed {
return inputStream.use {
jsonIgnoreUnknwn.decodeFromStream(MicroTwitterFeed.serializer(), it.buffered())
}
}

@Benchmark
fun decodeMicroTwitterJacksonStream(): MicroTwitterFeed {
return inputStream.use {
objectMapper.readValue(it, MicroTwitterFeed::class.java)
}
}
}
5 changes: 5 additions & 0 deletions formats/json/api/kotlinx-serialization-json.api
Original file line number Diff line number Diff line change
Expand Up @@ -340,3 +340,8 @@ public abstract class kotlinx/serialization/json/JsonTransformingSerializer : ko
protected fun transformSerialize (Lkotlinx/serialization/json/JsonElement;)Lkotlinx/serialization/json/JsonElement;
}

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

Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ public sealed class Json(
* @throws [SerializationException] if the given JSON string cannot be deserialized to the value of type [T].
*/
public final override fun <T> decodeFromString(deserializer: DeserializationStrategy<T>, string: String): T {
val lexer = JsonLexer(string)
val lexer = StringJsonLexer(string)
val input = StreamingJsonDecoder(this, WriteMode.OBJ, lexer, deserializer.descriptor)
val result = input.decodeSerializableValue(deserializer)
lexer.expectEof()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,32 @@
* Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

@file:OptIn(ExperimentalSerializationApi::class)
package kotlinx.serialization.json.internal

import kotlinx.serialization.*
import kotlinx.serialization.json.*
import kotlin.jvm.*
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import kotlin.jvm.JvmField

internal fun Composer(sb: JsonStringBuilder, json: Json): Composer =
if (json.configuration.prettyPrint) ComposerWithPrettyPrint(sb, json) else Composer(sb)

@OptIn(ExperimentalSerializationApi::class)
internal open class Composer(@JvmField internal val sb: JsonStringBuilder, @JvmField internal val json: Json) {
private var level = 0
internal open class Composer(@JvmField internal val sb: JsonStringBuilder) {
var writingFirst = true
private set
protected set

fun indent() {
open fun indent() {
writingFirst = true
level++
}

fun unIndent() {
level--
}
open fun unIndent() = Unit

fun nextItem() {
open fun nextItem() {
writingFirst = false
if (json.configuration.prettyPrint) {
print("\n")
repeat(level) { print(json.configuration.prettyPrintIndent) }
}
}

fun space() {
if (json.configuration.prettyPrint)
print(' ')
}
open fun space() = Unit

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

@ExperimentalUnsignedTypes
internal class ComposerForUnsignedNumbers(sb: JsonStringBuilder, json: Json) : Composer(sb, json) {
internal class ComposerForUnsignedNumbers(sb: JsonStringBuilder) : Composer(sb) {
override fun print(v: Int) {
return super.print(v.toUInt().toString())
}
Expand All @@ -66,3 +59,29 @@ internal class ComposerForUnsignedNumbers(sb: JsonStringBuilder, json: Json) : C
return super.print(v.toUShort().toString())
}
}

internal class ComposerWithPrettyPrint(
sb: JsonStringBuilder,
private val json: Json
) : Composer(sb) {
private var level = 0

override fun indent() {
writingFirst = true
level++
}

override fun unIndent() {
level--
}

override fun nextItem() {
writingFirst = false
print("\n")
repeat(level) { print(json.configuration.prettyPrintIndent) }
}

override fun space() {
print(' ')
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ internal fun JsonDecodingException(offset: Int, message: String) =
*/
internal class JsonEncodingException(message: String) : JsonException(message)

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

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

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

private fun String.minify(offset: Int = -1): String {
private fun CharSequence.minify(offset: Int = -1): CharSequence {
if (length < 200) return this
if (offset == -1) {
val start = this.length - 60
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import kotlinx.serialization.json.*
@OptIn(ExperimentalSerializationApi::class)
internal class JsonTreeReader(
configuration: JsonConfiguration,
private val lexer: JsonLexer
private val lexer: AbstractJsonLexer
) {
private val isLenient = configuration.isLenient
private var stackDepth = 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ import kotlinx.serialization.modules.*
import kotlin.jvm.*

/**
* [JsonDecoder] which reads given JSON from [JsonLexer] field by field.
* [JsonDecoder] which reads given JSON from [AbstractJsonLexer] field by field.
*/
@OptIn(ExperimentalSerializationApi::class, ExperimentalUnsignedTypes::class)
internal open class StreamingJsonDecoder(
final override val json: Json,
private val mode: WriteMode,
@JvmField internal val lexer: JsonLexer,
@JvmField internal val lexer: AbstractJsonLexer,
descriptor: SerialDescriptor
) : JsonDecoder, AbstractDecoder() {

Expand Down Expand Up @@ -256,7 +256,7 @@ internal open class StreamingJsonDecoder(
@OptIn(ExperimentalSerializationApi::class)
@ExperimentalUnsignedTypes
internal class JsonDecoderForUnsignedTypes(
private val lexer: JsonLexer,
private val lexer: AbstractJsonLexer,
json: Json
) : AbstractDecoder() {
override val serializersModule: SerializersModule = json.serializersModule
Expand All @@ -268,7 +268,7 @@ internal class JsonDecoderForUnsignedTypes(
override fun decodeShort(): Short = lexer.parseString("UShort") { toUShort().toShort() }
}

private inline fun <T> JsonLexer.parseString(expectedType: String, block: String.() -> T): T {
private inline fun <T> AbstractJsonLexer.parseString(expectedType: String, block: String.() -> T): T {
val input = consumeStringLenient()
try {
return input.block()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,10 +160,7 @@ internal class StreamingJsonEncoder(

override fun encodeInline(inlineDescriptor: SerialDescriptor): Encoder =
if (inlineDescriptor.isUnsignedNumber) StreamingJsonEncoder(
ComposerForUnsignedNumbers(
composer.sb,
json
), json, mode, null
ComposerForUnsignedNumbers(composer.sb), json, mode, null
)
else super.encodeInline(inlineDescriptor)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ private sealed class AbstractJsonTreeDecoder(

@OptIn(ExperimentalUnsignedTypes::class)
override fun decodeTaggedInline(tag: String, inlineDescriptor: SerialDescriptor): Decoder =
if (inlineDescriptor.isUnsignedNumber) JsonDecoderForUnsignedTypes(JsonLexer(getPrimitiveValue(tag).content), json)
if (inlineDescriptor.isUnsignedNumber) JsonDecoderForUnsignedTypes(StringJsonLexer(getPrimitiveValue(tag).content), json)
else super.decodeTaggedInline(tag, inlineDescriptor)
}

Expand Down
Loading