diff options
Diffstat (limited to 'benchmark')
9 files changed, 594 insertions, 15 deletions
diff --git a/benchmark/build.gradle b/benchmark/build.gradle index 8e0e4927..751ad78c 100644 --- a/benchmark/build.gradle +++ b/benchmark/build.gradle @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + /* * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ @@ -6,27 +8,45 @@ apply plugin: 'java' apply plugin: 'kotlin' apply plugin: 'kotlinx-serialization' apply plugin: 'idea' -apply plugin: 'net.ltgt.apt' apply plugin: 'com.github.johnrengelman.shadow' -apply plugin: 'me.champeau.gradle.jmh' +apply plugin: 'me.champeau.jmh' sourceCompatibility = 1.8 targetCompatibility = 1.8 -jmh.jmhVersion = 1.22 +jmh.jmhVersion = "1.35" + +processJmhResources { + doFirst { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + } +} jmhJar { - baseName 'benchmarks' - classifier = null - version = null - destinationDir = file("$rootDir") + archiveBaseName.set('benchmarks') + archiveVersion.set('') + destinationDirectory = file("$rootDir") +} + +// to include benchmark-module jmh source set compilation during build to verify that it is also compiled succesfully +assemble.dependsOn jmhClasses + +tasks.withType(KotlinCompile).configureEach { + kotlinOptions { + if (rootProject.ext.kotlin_lv_override != null) { + languageVersion = rootProject.ext.kotlin_lv_override + freeCompilerArgs += "-Xsuppress-version-warnings" + } + } } dependencies { - implementation 'org.openjdk.jmh:jmh-core:1.22' - implementation 'com.google.guava:guava:24.1.1-jre' - implementation 'com.fasterxml.jackson.core:jackson-databind:2.12.1' - implementation 'com.fasterxml.jackson.module:jackson-module-kotlin:2.12.1' + implementation 'org.openjdk.jmh:jmh-core:1.35' + implementation 'com.google.guava:guava:31.1-jre' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.3' + implementation 'com.fasterxml.jackson.module:jackson-module-kotlin:2.13.3' + implementation "com.squareup.okio:okio:$okio_version" implementation project(':kotlinx-serialization-core') implementation project(':kotlinx-serialization-json') + implementation project(':kotlinx-serialization-json-okio') implementation project(':kotlinx-serialization-protobuf') } diff --git a/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/ContextualOverheadBenchmark.kt b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/ContextualOverheadBenchmark.kt new file mode 100644 index 00000000..2773cfc2 --- /dev/null +++ b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/ContextualOverheadBenchmark.kt @@ -0,0 +1,72 @@ +package kotlinx.benchmarks.json + +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.buildClassSerialDescriptor +import kotlinx.serialization.descriptors.element +import kotlinx.serialization.encoding.* +import kotlinx.serialization.json.* +import kotlinx.serialization.modules.* +import org.openjdk.jmh.annotations.* +import java.util.concurrent.* + +@Warmup(iterations = 7, time = 1) +@Measurement(iterations = 5, time = 1) +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@State(Scope.Benchmark) +@Fork(1) +open class ContextualOverheadBenchmark { + @Serializable + data class Holder(val data: @Contextual Data) + + class Data(val a: Int, val b: String) + + object DataSerializer: KSerializer<Data> { + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Serializer") { + element<Int>("a") + element<String>("b") + } + + override fun deserialize(decoder: Decoder): Data { + return decoder.decodeStructure(descriptor) { + var a = 0 + var b = "" + while (true) { + when (val index = decodeElementIndex(descriptor)) { + 0 -> a = decodeIntElement(descriptor, 0) + 1 -> b = decodeStringElement(descriptor, 1) + CompositeDecoder.DECODE_DONE -> break + else -> error("Unexpected index: $index") + } + } + Data(a, b) + } + } + + override fun serialize(encoder: Encoder, value: Data) { + encoder.encodeStructure(descriptor) { + encodeIntElement(descriptor, 0, value.a) + encodeStringElement(descriptor, 1, value.b) + } + } + + } + + private val module = SerializersModule { + contextual(DataSerializer) + } + + private val json = Json { serializersModule = module } + + private val holder = Holder(Data(1, "abc")) + private val holderString = json.encodeToString(holder) + private val holderSerializer = serializer<Holder>() + + @Benchmark + fun decode() = json.decodeFromString(holderSerializer, holderString) + + @Benchmark + fun encode() = json.encodeToString(holderSerializer, holder) + +} diff --git a/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/JacksonComparisonBenchmark.kt b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/JacksonComparisonBenchmark.kt index b8125001..d162418c 100644 --- a/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/JacksonComparisonBenchmark.kt +++ b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/JacksonComparisonBenchmark.kt @@ -4,7 +4,12 @@ import com.fasterxml.jackson.databind.* import com.fasterxml.jackson.module.kotlin.* import kotlinx.serialization.* import kotlinx.serialization.json.* +import kotlinx.serialization.json.okio.encodeToBufferedSink +import okio.blackholeSink +import okio.buffer import org.openjdk.jmh.annotations.* +import java.io.ByteArrayInputStream +import java.io.OutputStream import java.util.concurrent.* @Warmup(iterations = 7, time = 1) @@ -63,7 +68,15 @@ open class JacksonComparisonBenchmark { cookies = "_ga=GA1.2.971852807.1546968515" ) + private val devNullSink = blackholeSink().buffer() + private val devNullStream = object : OutputStream() { + override fun write(b: Int) {} + override fun write(b: ByteArray) {} + override fun write(b: ByteArray, off: Int, len: Int) {} + } + private val stringData = Json.encodeToString(DefaultPixelEvent.serializer(), data) + private val utf8BytesData = stringData.toByteArray() @Serializable private class SmallDataClass(val id: Int, val name: String) @@ -83,12 +96,27 @@ open class JacksonComparisonBenchmark { fun kotlinToString(): String = Json.encodeToString(DefaultPixelEvent.serializer(), data) @Benchmark + fun kotlinToStream() = Json.encodeToStream(DefaultPixelEvent.serializer(), data, devNullStream) + + @Benchmark + fun kotlinFromStream() = Json.decodeFromStream(DefaultPixelEvent.serializer(), ByteArrayInputStream(utf8BytesData)) + + @Benchmark + fun kotlinToOkio() = Json.encodeToBufferedSink(DefaultPixelEvent.serializer(), data, devNullSink) + + @Benchmark fun kotlinToStringWithEscapes(): String = Json.encodeToString(DefaultPixelEvent.serializer(), dataWithEscapes) @Benchmark fun kotlinSmallToString(): String = Json.encodeToString(SmallDataClass.serializer(), smallData) @Benchmark + fun kotlinSmallToStream() = Json.encodeToStream(SmallDataClass.serializer(), smallData, devNullStream) + + @Benchmark + fun kotlinSmallToOkio() = Json.encodeToBufferedSink(SmallDataClass.serializer(), smallData, devNullSink) + + @Benchmark fun jacksonFromString(): DefaultPixelEvent = objectMapper.readValue(stringData, DefaultPixelEvent::class.java) @Benchmark diff --git a/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/LookupOverheadBenchmark.kt b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/LookupOverheadBenchmark.kt new file mode 100644 index 00000000..ddfc5792 --- /dev/null +++ b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/LookupOverheadBenchmark.kt @@ -0,0 +1,69 @@ +/* + * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.benchmarks.json + +import kotlinx.serialization.* +import kotlinx.serialization.builtins.* +import kotlinx.serialization.json.* +import org.openjdk.jmh.annotations.* +import java.util.concurrent.* + +@Warmup(iterations = 7, time = 1) +@Measurement(iterations = 7, time = 1) +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@State(Scope.Benchmark) +@Fork(2) +open class LookupOverheadBenchmark { + + @Serializable + class Holder(val a: String) + + @Serializable + class Generic<T>(val a: T) + + @Serializable + class DoubleGeneric<T1, T2>(val a: T1, val b: T2) + + @Serializable + class PentaGeneric<T1, T2, T3, T4, T5>(val a: T1, val b: T2, val c: T3, val d: T4, val e: T5) + + private val data = """{"a":""}""" + private val doubleData = """{"a":"","b":0}""" + private val pentaData = """{"a":"","b":0,"c":1,"d":true,"e":" "}""" + + @Serializable + object Object + + @Benchmark + fun dataReified() = Json.decodeFromString<Holder>(data) + + @Benchmark + fun dataPlain() = Json.decodeFromString(Holder.serializer(), data) + + @Benchmark + fun genericReified() = Json.decodeFromString<Generic<String>>(data) + + @Benchmark + fun genericPlain() = Json.decodeFromString(Generic.serializer(String.serializer()), data) + + @Benchmark + fun doubleGenericReified() = Json.decodeFromString<DoubleGeneric<String, Int>>(doubleData) + + @Benchmark + fun doubleGenericPlain() = Json.decodeFromString(DoubleGeneric.serializer(String.serializer(), Int.serializer()), doubleData) + + @Benchmark + fun pentaGenericReified() = Json.decodeFromString<PentaGeneric<String, Int, Long, Boolean, Char>>(pentaData) + + @Benchmark + fun pentaGenericPlain() = Json.decodeFromString(PentaGeneric.serializer(String.serializer(), Int.serializer(), Long.serializer(), Boolean.serializer(), Char.serializer()), pentaData) + + @Benchmark + fun objectReified() = Json.decodeFromString<Object>("{}") + + @Benchmark + fun objectPlain() = Json.decodeFromString(Object.serializer(), "{}") +} diff --git a/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/PolymorphismOverheadBenchmark.kt b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/PolymorphismOverheadBenchmark.kt new file mode 100644 index 00000000..9af856d3 --- /dev/null +++ b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/PolymorphismOverheadBenchmark.kt @@ -0,0 +1,70 @@ +package kotlinx.benchmarks.json + +import kotlinx.serialization.* +import kotlinx.serialization.json.* +import kotlinx.serialization.modules.* +import org.openjdk.jmh.annotations.* +import java.util.concurrent.* + +@Warmup(iterations = 7, time = 1) +@Measurement(iterations = 5, time = 1) +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@State(Scope.Benchmark) +@Fork(1) +open class PolymorphismOverheadBenchmark { + + @Serializable + @JsonClassDiscriminator("poly") + data class PolymorphicWrapper(val i: @Polymorphic Poly, val i2: Impl) // amortize the cost a bit + + @Serializable + data class SimpleWrapper(val poly: @Polymorphic Poly) + + @Serializable + data class BaseWrapper(val i: Impl, val i2: Impl) + + @JsonClassDiscriminator("poly") + interface Poly + + @Serializable + @JsonClassDiscriminator("poly") + class Impl(val a: Int, val b: String) : Poly + + private val impl = Impl(239, "average_size_string") + private val module = SerializersModule { + polymorphic(Poly::class) { + subclass(Impl.serializer()) + } + } + + private val json = Json { serializersModule = module } + private val implString = json.encodeToString(impl) + private val polyString = json.encodeToString<Poly>(impl) + private val serializer = serializer<Poly>() + + private val wrapper = SimpleWrapper(Impl(1, "abc")) + private val wrapperString = json.encodeToString(wrapper) + private val wrapperSerializer = serializer<SimpleWrapper>() + + + // 5000 + @Benchmark + fun base() = json.decodeFromString(Impl.serializer(), implString) + + // As of 1.3.x + // Baseline -- 1500 + // v1, no skip -- 2000 + // v2, with skip -- 3000 [withdrawn] + @Benchmark + fun poly() = json.decodeFromString(serializer, polyString) + + // test for child polymorphic serializer in decoding + @Benchmark + fun polyChildDecode() = json.decodeFromString(wrapperSerializer, wrapperString) + + // test for child polymorphic serializer in encoding + @Benchmark + fun polyChildEncode() = json.encodeToString(wrapperSerializer, wrapper) + +} diff --git a/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/TwitterBenchmark.kt b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/TwitterBenchmark.kt index 5c930ec6..90889fe8 100644 --- a/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/TwitterBenchmark.kt +++ b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/TwitterBenchmark.kt @@ -3,6 +3,7 @@ package kotlinx.benchmarks.json import kotlinx.benchmarks.model.* import kotlinx.serialization.json.* import org.openjdk.jmh.annotations.* +import java.io.OutputStream import java.util.concurrent.* @Warmup(iterations = 7, time = 1) @@ -24,6 +25,12 @@ open class TwitterBenchmark { private val jsonImplicitNulls = Json { explicitNulls = false } + private val devNullStream = object : OutputStream() { + override fun write(b: Int) {} + override fun write(b: ByteArray) {} + override fun write(b: ByteArray, off: Int, len: Int) {} + } + @Setup fun init() { require(twitter == Json.decodeFromString(Twitter.serializer(), Json.encodeToString(Twitter.serializer(), twitter))) @@ -38,4 +45,7 @@ open class TwitterBenchmark { @Benchmark fun encodeTwitter() = Json.encodeToString(Twitter.serializer(), twitter) + + @Benchmark + fun encodeTwitterStream() = Json.encodeToStream(Twitter.serializer(), twitter, devNullStream) } diff --git a/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/TwitterFeedBenchmark.kt b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/TwitterFeedBenchmark.kt index e015ad96..837a8ba3 100644 --- a/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/TwitterFeedBenchmark.kt +++ b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/TwitterFeedBenchmark.kt @@ -3,8 +3,6 @@ package kotlinx.benchmarks.json import kotlinx.benchmarks.model.* import kotlinx.serialization.* import kotlinx.serialization.json.* -import kotlinx.serialization.json.Json.Default.decodeFromString -import kotlinx.serialization.json.Json.Default.encodeToString import org.openjdk.jmh.annotations.* import java.util.concurrent.* @@ -24,19 +22,25 @@ open class TwitterFeedBenchmark { */ private val input = TwitterFeedBenchmark::class.java.getResource("/twitter_macro.json").readBytes().decodeToString() private val twitter = Json.decodeFromString(MacroTwitterFeed.serializer(), input) + private val jsonNoAltNames = Json { useAlternativeNames = false } private val jsonIgnoreUnknwn = Json { ignoreUnknownKeys = true } - private val jsonIgnoreUnknwnNoAltNames = Json { ignoreUnknownKeys = true; useAlternativeNames = false} + private val jsonIgnoreUnknwnNoAltNames = Json { ignoreUnknownKeys = true; useAlternativeNames = false } + private val jsonNamingStrategy = Json { namingStrategy = JsonNamingStrategy.SnakeCase } + private val jsonNamingStrategyIgnoreUnknwn = Json(jsonNamingStrategy) { ignoreUnknownKeys = true } + + private val twitterKt = jsonNamingStrategy.decodeFromString(MacroTwitterFeedKt.serializer(), input) @Setup fun init() { require(twitter == Json.decodeFromString(MacroTwitterFeed.serializer(), Json.encodeToString(MacroTwitterFeed.serializer(), twitter))) } - // Order of magnitude: ~400 op/s + // Order of magnitude: ~500 op/s @Benchmark fun decodeTwitter() = Json.decodeFromString(MacroTwitterFeed.serializer(), input) + // Should be the same as decodeTwitter, since decodeTwitter never hit unknown name and therefore should never build deserializationNamesMap anyway @Benchmark fun decodeTwitterNoAltNames() = jsonNoAltNames.decodeFromString(MacroTwitterFeed.serializer(), input) @@ -46,7 +50,20 @@ open class TwitterFeedBenchmark { @Benchmark fun decodeMicroTwitter() = jsonIgnoreUnknwn.decodeFromString(MicroTwitterFeed.serializer(), input) + // Should be faster than decodeMicroTwitter, as we explicitly opt-out from deserializationNamesMap on unknown name @Benchmark fun decodeMicroTwitterNoAltNames() = jsonIgnoreUnknwnNoAltNames.decodeFromString(MicroTwitterFeed.serializer(), input) + // Should be just a bit slower than decodeMicroTwitter, because alternative names map is created in both cases + @Benchmark + fun decodeMicroTwitterWithNamingStrategy(): MicroTwitterFeedKt = jsonNamingStrategyIgnoreUnknwn.decodeFromString(MicroTwitterFeedKt.serializer(), input) + + // Can be slower than decodeTwitter, as we always build deserializationNamesMap when naming strategy is used + @Benchmark + fun decodeTwitterWithNamingStrategy(): MacroTwitterFeedKt = jsonNamingStrategy.decodeFromString(MacroTwitterFeedKt.serializer(), input) + + // 15-20% slower than without the strategy. Without serializationNamesMap (invoking strategy on every write), up to 50% slower + @Benchmark + fun encodeTwitterWithNamingStrategy(): String = jsonNamingStrategy.encodeToString(MacroTwitterFeedKt.serializer(), twitterKt) + } diff --git a/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/UseSerializerOverheadBenchmark.kt b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/UseSerializerOverheadBenchmark.kt new file mode 100644 index 00000000..bc3c89d7 --- /dev/null +++ b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/UseSerializerOverheadBenchmark.kt @@ -0,0 +1,123 @@ +@file:UseSerializers(UseSerializerOverheadBenchmark.DataClassSerializer::class, UseSerializerOverheadBenchmark.DataObjectSerializer::class) +package kotlinx.benchmarks.json + + +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.buildClassSerialDescriptor +import kotlinx.serialization.descriptors.element +import kotlinx.serialization.encoding.* +import kotlinx.serialization.json.* +import kotlinx.serialization.modules.* +import org.openjdk.jmh.annotations.* +import java.util.concurrent.* + +@Warmup(iterations = 7, time = 1) +@Measurement(iterations = 5, time = 1) +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@State(Scope.Benchmark) +@Fork(1) +open class UseSerializerOverheadBenchmark { + @Serializable + data class HolderForClass(val data: DataForClass) + + @Serializable + data class HolderForObject(val data: DataForObject) + + class DataForClass(val a: Int, val b: String) + + class DataForObject(val a: Int, val b: String) + + object DataClassSerializer: KSerializer<DataForClass> { + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("ClassSerializer") { + element<Int>("a") + element<String>("b") + } + + override fun deserialize(decoder: Decoder): DataForClass { + return decoder.decodeStructure(descriptor) { + var a = 0 + var b = "" + while (true) { + when (val index = decodeElementIndex(ContextualOverheadBenchmark.DataSerializer.descriptor)) { + 0 -> a = decodeIntElement(ContextualOverheadBenchmark.DataSerializer.descriptor, 0) + 1 -> b = decodeStringElement(ContextualOverheadBenchmark.DataSerializer.descriptor, 1) + CompositeDecoder.DECODE_DONE -> break + else -> error("Unexpected index: $index") + } + } + DataForClass(a, b) + } + } + + override fun serialize(encoder: Encoder, value: DataForClass) { + encoder.encodeStructure(descriptor) { + encodeIntElement(descriptor, 0, value.a) + encodeStringElement(descriptor, 1, value.b) + } + } + } + + object DataObjectSerializer: KSerializer<DataForObject> { + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("ObjectSerializer") { + element<Int>("a") + element<String>("b") + } + + override fun deserialize(decoder: Decoder): DataForObject { + return decoder.decodeStructure(descriptor) { + var a = 0 + var b = "" + while (true) { + when (val index = decodeElementIndex(ContextualOverheadBenchmark.DataSerializer.descriptor)) { + 0 -> a = decodeIntElement(ContextualOverheadBenchmark.DataSerializer.descriptor, 0) + 1 -> b = decodeStringElement(ContextualOverheadBenchmark.DataSerializer.descriptor, 1) + CompositeDecoder.DECODE_DONE -> break + else -> error("Unexpected index: $index") + } + } + DataForObject(a, b) + } + } + + override fun serialize(encoder: Encoder, value: DataForObject) { + encoder.encodeStructure(descriptor) { + encodeIntElement(descriptor, 0, value.a) + encodeStringElement(descriptor, 1, value.b) + } + } + } + + private val module = SerializersModule { + contextual(DataClassSerializer) + } + + private val json = Json { serializersModule = module } + + private val classHolder = HolderForClass(DataForClass(1, "abc")) + private val classHolderString = json.encodeToString(classHolder) + private val classHolderSerializer = serializer<HolderForClass>() + + private val objectHolder = HolderForObject(DataForObject(1, "abc")) + private val objectHolderString = json.encodeToString(objectHolder) + private val objectHolderSerializer = serializer<HolderForObject>() + + @Benchmark + fun decodeForClass() = json.decodeFromString(classHolderSerializer, classHolderString) + + @Benchmark + fun encodeForClass() = json.encodeToString(classHolderSerializer, classHolder) + + /* + Any optimizations should not affect the speed of these tests. + It doesn't make sense to cache singleton (`object`) serializer, because the object is accessed instantly + */ + + @Benchmark + fun decodeForObject() = json.decodeFromString(objectHolderSerializer, objectHolderString) + + @Benchmark + fun encodeForObject() = json.encodeToString(objectHolderSerializer, objectHolder) + +} diff --git a/benchmark/src/jmh/kotlin/kotlinx/benchmarks/model/MacroTwitterUntailored.kt b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/model/MacroTwitterUntailored.kt new file mode 100644 index 00000000..fca69cc0 --- /dev/null +++ b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/model/MacroTwitterUntailored.kt @@ -0,0 +1,170 @@ +package kotlinx.benchmarks.model + +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +/** + * All model classes are the same as in MacroTwitter.kt but named accordingly to Kotlin naming policies to test JsonNamingStrategy performance. + * Only Size, SizeType and Urls are not copied + */ + +@Serializable +data class MacroTwitterFeedKt( + val statuses: List<TwitterStatusKt>, + val searchMetadata: SearchMetadata +) + +@Serializable +data class MicroTwitterFeedKt( + val statuses: List<TwitterTrimmedStatusKt> +) + +@Serializable +data class TwitterTrimmedStatusKt( + val metadata: MetadataKt, + val createdAt: String, + val id: Long, + val idStr: String, + val text: String, + val source: String, + val truncated: Boolean, + val user: TwitterTrimmedUserKt, + val retweetedStatus: TwitterTrimmedStatusKt? = null, +) + +@Serializable +data class TwitterStatusKt( + val metadata: MetadataKt, + val createdAt: String, + val id: Long, + val idStr: String, + val text: String, + val source: String, + val truncated: Boolean, + val inReplyToStatusId: Long?, + val inReplyToStatusIdStr: String?, + val inReplyToUserId: Long?, + val inReplyToUserIdStr: String?, + val inReplyToScreenName: String?, + val user: TwitterUserKt, + val geo: String?, + val coordinates: String?, + val place: String?, + val contributors: List<String>?, + val retweetedStatus: TwitterStatusKt? = null, + val retweetCount: Int, + val favoriteCount: Int, + val entities: StatusEntitiesKt, + val favorited: Boolean, + val retweeted: Boolean, + val lang: String, + val possiblySensitive: Boolean? = null +) + +@Serializable +data class StatusEntitiesKt( + val hashtags: List<Hashtag>, + val symbols: List<String>, + val urls: List<Url>, + val userMentions: List<TwitterUserMentionKt>, + val media: List<TwitterMediaKt>? = null +) + +@Serializable +data class TwitterMediaKt( + val id: Long, + val idStr: String, + val url: String, + val mediaUrl: String, + val mediaUrlHttps: String, + val expandedUrl: String, + val displayUrl: String, + val indices: List<Int>, + val type: String, + val sizes: SizeType, + val sourceStatusId: Long? = null, + val sourceStatusIdStr: String? = null +) + +@Serializable +data class TwitterUserMentionKt( + val screenName: String, + val name: String, + val id: Long, + val idStr: String, + val indices: List<Int> +) + +@Serializable +data class MetadataKt( + val resultType: String, + val isoLanguageCode: String +) + +@Serializable +data class TwitterTrimmedUserKt( + val id: Long, + val idStr: String, + val name: String, + val screenName: String, + val location: String, + val description: String, + val url: String?, + val entities: UserEntitiesKt, + val protected: Boolean, + val followersCount: Int, + val friendsCount: Int, + val listedCount: Int, + val createdAt: String, + val favouritesCount: Int, +) + +@Serializable +data class TwitterUserKt( + val id: Long, + val idStr: String, + val name: String, + val screenName: String, + val location: String, + val description: String, + val url: String?, + val entities: UserEntitiesKt, + val protected: Boolean, + val followersCount: Int, + val friendsCount: Int, + val listedCount: Int, + val createdAt: String, + val favouritesCount: Int, + val utcOffset: Int?, + val timeZone: String?, + val geoEnabled: Boolean, + val verified: Boolean, + val statusesCount: Int, + val lang: String, + val contributorsEnabled: Boolean, + val isTranslator: Boolean, + val isTranslationEnabled: Boolean, + val profileBackgroundColor: String, + val profileBackgroundImageUrl: String, + val profileBackgroundImageUrlHttps: String, + val profileBackgroundTile: Boolean, + val profileImageUrl: String, + val profileImageUrlHttps: String, + val profileBannerUrl: String? = null, + val profileLinkColor: String, + val profileSidebarBorderColor: String, + val profileSidebarFillColor: String, + val profileTextColor: String, + val profileUseBackgroundImage: Boolean, + val defaultProfile: Boolean, + val defaultProfileImage: Boolean, + val following: Boolean, + val followRequestSent: Boolean, + val notifications: Boolean +) + +@Serializable +data class UserEntitiesKt( + val url: Urls? = null, + val description: Urls +) |