summaryrefslogtreecommitdiff
path: root/formats/json-okio
diff options
context:
space:
mode:
authorSergey Shanshin <sergey.shanshin@jetbrains.com>2022-06-30 15:43:42 +0300
committerGitHub <noreply@github.com>2022-06-30 15:43:42 +0300
commit5e8ccad1f70a9457e0ffe6ae6b10a0bd0eaaa618 (patch)
treee522ba5e8da0cb4701f4a259d9f2ba5b43b42bf2 /formats/json-okio
parent605a35faa87534e24482aa00a52a2a71bebd51a3 (diff)
downloadkotlinx.serialization-5e8ccad1f70a9457e0ffe6ae6b10a0bd0eaaa618.tar.gz
Add Okio integration (#1901)
* Add okio integration as separate module json-okio * Extract separate module json-tests that tests both Java streams and okio * Rewrite Java stream writer so that it uses much faster in-place UTF8 encoder instead of java.io.Writer * Add benchmarks for streams and okio * Disable targets for tests that are not supported by okio (they can't be run on LinuxX64-86 hosts anyway)
Diffstat (limited to 'formats/json-okio')
-rw-r--r--formats/json-okio/api/kotlinx-serialization-json-okio.api7
-rw-r--r--formats/json-okio/build.gradle.kts31
-rw-r--r--formats/json-okio/commonMain/src/kotlinx/serialization/json/okio/OkioStreams.kt139
-rw-r--r--formats/json-okio/commonMain/src/kotlinx/serialization/json/okio/internal/OkioJsonStreams.kt59
-rw-r--r--formats/json-okio/gradle.properties1
5 files changed, 237 insertions, 0 deletions
diff --git a/formats/json-okio/api/kotlinx-serialization-json-okio.api b/formats/json-okio/api/kotlinx-serialization-json-okio.api
new file mode 100644
index 00000000..4b3f0a2c
--- /dev/null
+++ b/formats/json-okio/api/kotlinx-serialization-json-okio.api
@@ -0,0 +1,7 @@
+public final class kotlinx/serialization/json/okio/OkioStreamsKt {
+ public static final fun decodeFromSource (Lkotlinx/serialization/json/Json;Lkotlinx/serialization/DeserializationStrategy;Lokio/Source;)Ljava/lang/Object;
+ public static final fun decodeSourceToSequence (Lkotlinx/serialization/json/Json;Lokio/Source;Lkotlinx/serialization/DeserializationStrategy;Lkotlinx/serialization/json/DecodeSequenceMode;)Lkotlin/sequences/Sequence;
+ public static synthetic fun decodeSourceToSequence$default (Lkotlinx/serialization/json/Json;Lokio/Source;Lkotlinx/serialization/DeserializationStrategy;Lkotlinx/serialization/json/DecodeSequenceMode;ILjava/lang/Object;)Lkotlin/sequences/Sequence;
+ public static final fun encodeToSink (Lkotlinx/serialization/json/Json;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Lokio/Sink;)V
+}
+
diff --git a/formats/json-okio/build.gradle.kts b/formats/json-okio/build.gradle.kts
new file mode 100644
index 00000000..3e6565e9
--- /dev/null
+++ b/formats/json-okio/build.gradle.kts
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+import Java9Modularity.configureJava9ModuleInfo
+
+plugins {
+ kotlin("multiplatform")
+ kotlin("plugin.serialization")
+}
+
+apply(from = rootProject.file("gradle/native-targets.gradle"))
+apply(from = rootProject.file("gradle/configure-source-sets.gradle"))
+
+kotlin {
+ sourceSets {
+ val commonMain by getting {
+ dependencies {
+ api(project(":kotlinx-serialization-core"))
+ api(project(":kotlinx-serialization-json"))
+ implementation("com.squareup.okio:okio:${property("okio_version")}")
+ }
+ }
+ val commonTest by getting {
+ dependencies {
+ implementation("com.squareup.okio:okio:${property("okio_version")}")
+ }
+ }
+ }
+}
+
+project.configureJava9ModuleInfo()
diff --git a/formats/json-okio/commonMain/src/kotlinx/serialization/json/okio/OkioStreams.kt b/formats/json-okio/commonMain/src/kotlinx/serialization/json/okio/OkioStreams.kt
new file mode 100644
index 00000000..b666d839
--- /dev/null
+++ b/formats/json-okio/commonMain/src/kotlinx/serialization/json/okio/OkioStreams.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.json.okio
+
+import kotlinx.serialization.*
+import kotlinx.serialization.json.DecodeSequenceMode
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.internal.*
+import kotlinx.serialization.json.okio.internal.JsonToOkioStreamWriter
+import kotlinx.serialization.json.internal.decodeToSequenceByReader
+import kotlinx.serialization.json.okio.internal.OkioSerialReader
+import okio.*
+
+/**
+ * Serializes the [value] with [serializer] into a [target] using JSON format and UTF-8 encoding.
+ *
+ * If [target] is not a [BufferedSink] then called [Sink.buffer] for it to create buffered wrapper.
+ *
+ * @throws [SerializationException] if the given value cannot be serialized to JSON.
+ * @throws [okio.IOException] If an I/O error occurs and sink can't be written to.
+ */
+@ExperimentalSerializationApi
+public fun <T> Json.encodeToSink(
+ serializer: SerializationStrategy<T>,
+ value: T,
+ target: Sink
+) {
+ val buffered = if (target is BufferedSink) target else target.buffer()
+ val writer = JsonToOkioStreamWriter(buffered)
+ try {
+ encodeByWriter(writer, serializer, value)
+ } finally {
+ writer.release()
+ }
+}
+
+/**
+ * Serializes given [value] to a [target] using UTF-8 encoding and serializer retrieved from the reified type parameter.
+ *
+ * If [target] is not a [BufferedSink] then called [Sink.buffer] for it to create buffered wrapper.
+ *
+ * @throws [SerializationException] if the given value cannot be serialized to JSON.
+ * @throws [okio.IOException] If an I/O error occurs and sink can't be written to.
+ */
+@ExperimentalSerializationApi
+public inline fun <reified T> Json.encodeToSink(
+ value: T,
+ target: Sink
+): Unit = encodeToSink(serializersModule.serializer(), value, target)
+
+
+/**
+ * Deserializes JSON from [source] using UTF-8 encoding to a value of type [T] using [deserializer].
+ *
+ * If [source] is not a [BufferedSource] then called [Source.buffer] for it to create buffered wrapper.
+ *
+ * Note that this functions expects that exactly one object would be present in the source
+ * and throws an exception if there are any dangling bytes after an object.
+ *
+ * @throws [SerializationException] if the given JSON input cannot be deserialized to the value of type [T].
+ * @throws [okio.IOException] If an I/O error occurs and source can't be read from.
+ */
+@ExperimentalSerializationApi
+public fun <T> Json.decodeFromSource(
+ deserializer: DeserializationStrategy<T>,
+ source: Source
+): T {
+ val buffered = if (source is BufferedSource) source else source.buffer()
+ return decodeByReader(deserializer, OkioSerialReader(buffered))
+}
+
+/**
+ * Deserializes the contents of given [source] to the value of type [T] using UTF-8 encoding and
+ * deserializer retrieved from the reified type parameter.
+ *
+ * If [source] is not a [BufferedSource] then called [Source.buffer] for it to create buffered wrapper.
+ *
+ * Note that this functions expects that exactly one object would be present in the stream
+ * and throws an exception if there are any dangling bytes after an object.
+ *
+ * @throws [SerializationException] if the given JSON input cannot be deserialized to the value of type [T].
+ * @throws [okio.IOException] If an I/O error occurs and source can't be read from.
+ */
+@ExperimentalSerializationApi
+public inline fun <reified T> Json.decodeFromSource(source: Source): T =
+ decodeFromSource(serializersModule.serializer(), source)
+
+
+/**
+ * Transforms the given [source] into lazily deserialized sequence of elements of type [T] using UTF-8 encoding and [deserializer].
+ * Unlike [decodeFromSource], [source] is allowed to have more than one element, separated as [format] declares.
+ *
+ * If [source] is not a [BufferedSource] then called [Source.buffer] for it to create buffered wrapper.
+ *
+ * Elements must all be of type [T].
+ * Elements are parsed lazily when resulting [Sequence] is evaluated.
+ * Resulting sequence is tied to the stream and can be evaluated only once.
+ *
+ * **Resource caution:** this method neither closes the [source] when the parsing is finished nor provides a method to close it manually.
+ * It is a caller responsibility to hold a reference to a stream and close it. Moreover, because stream is parsed lazily,
+ * closing it before returned sequence is evaluated completely will result in [Exception] from decoder.
+ *
+ * @throws [SerializationException] if the given JSON input cannot be deserialized to the value of type [T].
+ * @throws [okio.IOException] If an I/O error occurs and source can't be read from.
+ */
+@ExperimentalSerializationApi
+public fun <T> Json.decodeSourceToSequence(
+ source: Source,
+ deserializer: DeserializationStrategy<T>,
+ format: DecodeSequenceMode = DecodeSequenceMode.AUTO_DETECT
+): Sequence<T> {
+ val buffered = if (source is BufferedSource) source else source.buffer()
+ return decodeToSequenceByReader(OkioSerialReader(buffered), deserializer, format)
+}
+
+/**
+ * Transforms the given [source] into lazily deserialized sequence of elements of type [T] using UTF-8 encoding and deserializer retrieved from the reified type parameter.
+ * Unlike [decodeFromSource], [source] is allowed to have more than one element, separated as [format] declares.
+ *
+ * If [source] is not a [BufferedSource] then called [Source.buffer] for it to create buffered wrapper.
+ *
+ * Elements must all be of type [T].
+ * Elements are parsed lazily when resulting [Sequence] is evaluated.
+ * Resulting sequence is tied to the stream and constrained to be evaluated only once.
+ *
+ * **Resource caution:** this method does not close [source] when the parsing is finished neither provides method to close it manually.
+ * It is a caller responsibility to hold a reference to a stream and close it. Moreover, because stream is parsed lazily,
+ * closing it before returned sequence is evaluated fully would result in [Exception] from decoder.
+ *
+ * @throws [SerializationException] if the given JSON input cannot be deserialized to the value of type [T].
+ * @throws [okio.IOException] If an I/O error occurs and source can't be read from.
+ */
+@ExperimentalSerializationApi
+public inline fun <reified T> Json.decodeSourceToSequence(
+ source: Source,
+ format: DecodeSequenceMode = DecodeSequenceMode.AUTO_DETECT
+): Sequence<T> = decodeSourceToSequence(source, serializersModule.serializer(), format)
diff --git a/formats/json-okio/commonMain/src/kotlinx/serialization/json/okio/internal/OkioJsonStreams.kt b/formats/json-okio/commonMain/src/kotlinx/serialization/json/okio/internal/OkioJsonStreams.kt
new file mode 100644
index 00000000..3537392b
--- /dev/null
+++ b/formats/json-okio/commonMain/src/kotlinx/serialization/json/okio/internal/OkioJsonStreams.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
+
+package kotlinx.serialization.json.okio.internal
+
+import kotlinx.serialization.json.internal.ESCAPE_STRINGS
+import kotlinx.serialization.json.internal.JsonWriter
+import kotlinx.serialization.json.internal.SerialReader
+import okio.*
+
+internal class JsonToOkioStreamWriter(private val target: BufferedSink) : JsonWriter {
+ override fun writeLong(value: Long) {
+ write(value.toString())
+ }
+
+ override fun writeChar(char: Char) {
+ target.writeUtf8CodePoint(char.code)
+ }
+
+ override fun write(text: String) {
+ target.writeUtf8(text)
+ }
+
+ override fun writeQuoted(text: String) {
+ target.writeUtf8CodePoint('"'.code)
+ var lastPos = 0
+ for (i in text.indices) {
+ val c = text[i].code
+ if (c < ESCAPE_STRINGS.size && ESCAPE_STRINGS[c] != null) {
+ target.writeUtf8(text, lastPos, i) // flush prev
+ target.writeUtf8(ESCAPE_STRINGS[c]!!)
+ lastPos = i + 1
+ }
+ }
+
+ if (lastPos != 0) target.writeUtf8(text, lastPos, text.length)
+ else target.writeUtf8(text)
+ target.writeUtf8CodePoint('"'.code)
+ }
+
+ override fun release() {
+ target.flush()
+ }
+}
+
+internal class OkioSerialReader(private val source: BufferedSource): SerialReader {
+ override fun read(buffer: CharArray, bufferOffset: Int, count: Int): Int {
+ var i = 0
+ while (i < count && !source.exhausted()) {
+ buffer[i] = source.readUtf8CodePoint().toChar()
+ i++
+ }
+ return if (i > 0) i else -1
+ }
+}
+
diff --git a/formats/json-okio/gradle.properties b/formats/json-okio/gradle.properties
new file mode 100644
index 00000000..9965c652
--- /dev/null
+++ b/formats/json-okio/gradle.properties
@@ -0,0 +1 @@
+kotlin.mpp.enableCompatibilityMetadataVariant=false