kotlinx.serialization — ett multiplattformsbibliotek från JetBrains för att konvertera Kotlin-objekt till JSON, ProtoBuf, CBOR och andra format utan reflektion. Till skillnad från Gson och Moshi genererar den serialiseringskod vid kompilering genom @Serializable-annoteringen, vilket ger hög prestanda och typsäkerhet. Enligt GitHub Kotlin/kotlinx.serialization stöder biblioteket Kotlin/JVM, Kotlin/Native, Kotlin/JS och Kotlin/Wasm.
Huvudpunkter
kotlinx.serialization — är ett inbyggt serialiseringsbibliotek för Kotlin, utvecklat av JetBrains som en del av det officiella Kotlin-ekosystemet. Dess huvudsakliga skillnad från tredjepartslösningar (Gson, Moshi, Jackson) är att den inte använder reflektion under körning. Istället genereras serialiseringskoden vid kompilering med hjälp av Kotlin Symbol Processing (KSP) eller Kotlin Compiler Plugin. Detta ger en prestandaökning på upp till 3-5 gånger jämfört med Gson och garanterar typsäkerhet.
Biblioteket stöder officiellt fyra format: JSON (genom modulen kotlinx-serialization-json), ProtoBuf (kotlinx-serialization-protobuf), CBOR (kotlinx-serialization-cbor) och HOCON (kotlinx-serialization-hocon). Format läggs till som separata beroenden i build.gradle.kts, vilket gör att onödiga bibliotek inte dras in i projektet. För varje format finns en egen uppsättning konfigurationsparametrar.
Multiplattform — bibliotekets viktigaste funktion. Samma klass med @Serializable fungerar på alla målplattformar: JVM (Android, Backend), Native (iOS), JS (Web, React) och Wasm (WebAssembly). Utvecklaren behöver inte skriva olika serialiseringsimplementeringar för varje plattform — koden förblir enhetlig. Detta är särskilt värdefullt i Kotlin Multiplatform Mobile (KMM)-projekt, där delad kod används mellan Android och iOS.
Kodgenerering i kotlinx.serialization sker i tre steg. I första steget upptäcker Kotlin-kompilatorn @Serializable-annoteringen på en klass och skickar den till Kotlin Symbol Processing (KSP)-pluginen. I andra steget genererar KSP ett serialiserarobjekt som implementerar KSerializer-gränssnittet. I tredje steget kompileras den genererade koden tillsammans med projektets källkod. Som ett resultat utförs inget av dessa steg under körningen av applikationen.
Den genererade serialiseraren arbetar direkt med klassens fält genom deras getters och setters, utan reflektion. Detta innebär att fält med modifieraren private också serialiseras om de är markerade med @Serializable. Prestandan för detta tillvägagångssätt ligger nära manuell serialisering: för enkla klasser (5-10 fält) är serialiseringstiden 10-50 mikrosekunder, för komplexa objektdiagram — upp till 200 mikrosekunder per 1000 objekt.
För att ansluta biblioteket i ett Android- eller Kotlin/JVM-projekt måste du lägga till plugin och beroenden i build.gradle.kts. Pluginen org.jetbrains.kotlin.plugin.serialization i en version som matchar Kotlin-versionen aktiverar kodgenerering. Biblioteket kotlinx-serialization-json läggs till i dependencies-sektionen med en version som är oberoende av Kotlin-versionen.
// build.gradle.kts — anslutning av kotlinx.serialization
plugins {
val kotlinVersion = "2.1.0"
kotlin("jvm") version kotlinVersion
kotlin("plugin.serialization") version kotlinVersion
}
dependencies {
// Huvudmodul för serialisering
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
// Ytterligare format
implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:1.7.3")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-cbor:1.7.3")
}
JSON — det populäraste formatet i kotlinx.serialization. För att serialisera ett objekt räcker det att placera @Serializable-annoteringen på en data class och anropa Json.encodeToString(). För deserialisering — Json.decodeFromString() med angivande av typ. Biblioteket hanterar automatiskt null-fält, listor, nästlade objekt och enum. Alla klassens fält är som standard obligatoriska, om inte annat anges.
JSON-konfiguration utförs genom Json {} builder. I konstruktorn kan man skicka ignoreUnknownKeys = true för att hoppa över okända fält vid deserialisering, prettyPrint = true för formaterad utdata, coerceInputValues = true för att konvertera felaktiga värden till standardvärden. Även inställningarna encodeDefaults (serialisering av fält med standardvärden) och classDiscriminator (fältnamn för polymorf serialisering) är tillgängliga.
// Exempel på JSON-serialisering och deserialisering
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
@Serializable
data class Project(
val name: String,
val stars: Int,
val isActive: Boolean = true,
val languages: List<String> = emptyList()
)
fun main() {
val project = Project(
name = "kotlinx.serialization",
stars = 7200,
languages = listOf("Kotlin", "Java")
)
// Serialisering till JSON med prettyPrint
val json = Json { prettyPrint = true }
val jsonString = json.encodeToString(project)
println(jsonString)
/*
{
"name": "kotlinx.serialization",
"stars": 7200,
"isActive": true,
"languages": ["Kotlin", "Java"]
}
*/
// Deserialisering från JSON
val decoded = json.decodeFromString<Project>(jsonString)
println(decoded.name) // kotlinx.serialization
}
Exemplet visar den grundläggande cykeln för serialisering och deserialisering. Data class Project med @Serializable-annoteringen får automatiskt encodeToString och decodeFromString. Fältet isActive har standardvärdet true — om detta fält saknas i JSON används standardvärdet. Om okända fält kommer i JSON utan ignoreUnknownKeys = true kommer ett SerializationException att kastas.
Sealed class — ett av de kraftfullaste användningsområdena för kotlinx.serialization. Biblioteket stöder polymorf serialisering för sealed class-hierarkier utan extra konfiguration: markera sealed class och alla dess underklasser med @Serializable. Vid serialisering läggs fältet „type” (konfigurerbart via classDiscriminator) till, baserat på vilket den konkreta typen bestäms vid deserialisering.
// Polymorf serialisering av sealed class
@Serializable
sealed class Response
@Serializable
data class Success(val data: String) : Response()
@Serializable
data class Error(val code: Int, val message: String) : Response()
fun main() {
val json = Json { classDiscriminator = "result_type" }
val responses: List<Response> = listOf(
Success(data = "Data loaded"),
Error(code = 404, message = "Not found")
)
val jsonString = json.encodeToString(responses)
println(jsonString)
/*
[
{"result_type":"Success","data":"Data loaded"},
{"result_type":"Error","code":404,"message":"Not found"}
]
*/
val decoded = json.decodeFromString<List<Response>>(jsonString)
when (val first = decoded[0]) {
is Success -> println("Success: ${first.data}")
is Error -> println("Error: ${first.code}")
}
}
Polymorf serialisering av sealed class är särskilt användbar i API-klienter, där servern returnerar olika typer av svar. Utan kotlinx.serialization skulle du behöva skriva en manuell deserialiserare med when baserat på diskriminatorfältet. Med biblioteket görs detta med en annotering. classDiscriminator gör det möjligt att ändra namnet på markörfältet (standard „type”) till vilket värde som helst som förväntas av servern.
Biblioteket erbjuder en uppsättning annoteringar för finsjustering av serialisering. Den huvudsakliga är @Serializable för klassen. Ytterligare annoteringar: @SerialName för att ange fältnamn i JSON (om det skiljer sig från Kotlin-namnet), @Transient för att utesluta fält från serialisering, @Required för fält som måste finnas i JSON, @EncodeDefault för tvångsserialisering av fält med standardvärde.
| Annotering | Syfte | Exempel |
|---|---|---|
| @Serializable | Aktiverar generering av serialiserare för klassen | @Serializable data class User |
| @SerialName | Anger ett alternativt fältnamn i formatet | @SerialName(“user_name”) val name: String |
| @Transient | Utesluter fält från serialisering | @Transient val cache: MutableMap |
| @Required | Fält är obligatoriskt i JSON vid deserialisering | @Required val id: String |
| @EncodeDefault | Serialiserar fält även med standardvärde | @EncodeDefault val type: Type = Type.A |
| @Serializer | Kopplar en anpassad serialiserare till klassen | @Serializer(forClass = Date::class) |
Annoteringen @SerialName är avgörande när du arbetar med API där fältnamn är i snake_case och Kotlin-stilen är camelCase. Till exempel skickar servern “user_id” och i Kotlin-koden används userId. @SerialName(“user_id”) löser detta problem utan extra mappar. @Transient är användbart för fält som inte behöver skickas till servern — till exempel tillfälliga beräknade värden eller cache.
Som standard är alla fält i kotlinx.serialization obligatoriska. Om ett fält kan saknas i JSON måste du göra det nullable (String?) eller ange ett standardvärde (val name: String = “”). Det finns dock situationer när fältet inte är nullable i Kotlin men kan saknas i JSON på grund av API-versionhantering. I detta fall kastar @Required ett SerializationException när fältet saknas, medan standardvärdet fyller i default utan fel.
KSerializer — gränssnitt som implementeras av alla serialiserare i kotlinx.serialization. Om standard kodgenerering inte är lämplig (till exempel för arbete med Date, Bitmap eller specifikt binärt format), kan du skriva din egen serialiserare. För detta måste du implementera metoderna serialize() och deserialize(), samt tillhandahålla en deskriptor — en strukturbeskrivning för formatschemat.
Anpassade serialiserare kopplas in på två sätt: genom annoteringen @Serializable(with = MySerializer::class) för koppling till en specifik klass eller globalt via Json { serializersModule = ... } för koppling till alla instanser av typen. Det andra sättet är att föredra för inbyggda typer (Date, UUID) för att slippa skriva annotering på varje fält.
// Anpassad serialiserare för java.util.Date
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
object DateSerializer : KSerializer<Date> {
private val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US)
override val descriptor: SerialDescriptor =
PrimitiveSerialDescriptor("Date", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: Date) {
encoder.encodeString(dateFormat.format(value))
}
override fun deserialize(decoder: Decoder): Date {
return dateFormat.parse(decoder.decodeString())
}
}
// Användning av anpassad serialiserare
@Serializable
data class Event(
val title: String,
@Serializable(with = DateSerializer::class)
val date: Date
)
fun main() {
val json = Json { prettyPrint = true }
val event = Event("Släpp", Date())
println(json.encodeToString(event))
}
I exemplet konverterar DateSerializer java.util.Date till en ISO 8601-sträng. Utan en anpassad serialiserare kan kotlinx.serialization inte arbeta med Date — detta är en typ som inte ingår i standard Kotlin-biblioteket. @Serializable(with = DateSerializer::class) på ett specifikt fält kopplar serialiseraren endast för det fältet. För global registrering av alla Date, använd Json { serializersModule = SerializersModule { contextual(DateSerializer) } }.
kotlinx.serialization begränsar sig inte till JSON. Biblioteket stöder fyra inbyggda format, var och en med sin egen modul och konfiguration. JSON (kotlinx-serialization-json) — universell, läsbar för människor, lämplig för REST API. ProtoBuf (kotlinx-serialization-protobuf) — binär, kompakt, med obligatoriskt schema, för högbelastade mikrotjänster. CBOR (kotlinx-serialization-cbor) — binär motsvarighet till JSON, bekväm för IoT och mobila enheter med begränsad trafik. HOCON (kotlinx-serialization-hocon) — konfigurationsformat, kompatibelt med TypeSafe Config.
| Format | Modul | Typ | Schema | Typisk användning |
|---|---|---|---|---|
| JSON | kotlinx-serialization-json | Text | Valfritt | REST API, datalagring |
| ProtoBuf | kotlinx-serialization-protobuf | Binär | Obligatoriskt (.proto) | Mikrotjänster, gRPC |
| CBOR | kotlinx-serialization-cbor | Binär | Valfritt | IoT, mobila enheter |
| HOCON | kotlinx-serialization-hocon | Text | Valfritt | Konfigurationsfiler |
ProtoBuf kräver definition av schema i .proto-filer, men kotlinx-serialization-protobuf genererar Kotlin-klasser direkt från @Serializable utan .proto. Detta förenklar utvecklingen: annotera en data class och använd ProtoBuf.encodeToByteArray(). CBOR är särskilt relevant för Android-ramverket när du behöver överföra kompakta binära data via NFC eller BLE. Storleken på ett CBOR-meddelande är i genomsnitt 20-30% mindre än JSON för samma datamängd.
För REST API i en mobilapp är JSON optimalt — det felsöks utan extra verktyg, är läsbart i loggar och kompatibelt med vilken backend som helst. Om appen överför stora mängder data mellan mikrotjänster (hundratals megabyte) — ger ProtoBuf en hastighetsökning på upp till 5 gånger tack vare binär kodning. För lagring av inställningar i filer, använd HOCON eller JSON. För enheter med strikta trafikbegränsningar (IoT-sensorer) — CBOR.
Första misstaget — att ignorera okända nycklar vid deserialisering. Om servern har lagt till ett nytt fält och du har ignoreUnknownKeys = false, kraschar appen med SerializationException. Som standard är denna flagga avstängd. Lösning: ställ alltid in Json { ignoreUnknownKeys = true } för produktionskod för att vara motståndskraftig mot API-förändringar.
Andra misstaget — serialisering av internal eller private fält i data class. I en Kotlin data class serialiseras alla fält i primärkonstruktorn som standard. Om ett fält innehåller känsliga data (lösenord, token), måste du markera det med @Transient eller ta bort det från primärkonstruktorn. @Transient utesluter fältet helt från JSON, men i konstruktorn kan det orsaka ett fel — det är bättre att definiera sådana fält i klasskroppen med @Transient.
Tredje misstaget — polymorf serialisering utan sealed class. Om du använder open class istället för sealed, kräver kotlinx.serialization explicit registrering av alla underklasser i serializersModule. Till skillnad från sealed class, där kompilatorn känner till alla underklasser, tillåter open class godtycklig utökning — biblioteket kan inte automatiskt bestämma alla undertyper. Registrering görs via Json { serializersModule = SerializersModule { polymorphic(Base::class) { subclass(Derived::class) } } }.
Versionen av kotlinx.serialization måste vara kompatibel med Kotlin-versionen. JetBrains publicerar en kompatibilitetstabell: kotlinx-serialization 1.6.x är kompatibel med Kotlin 1.9.x, 1.7.x — med Kotlin 2.0.x och 2.1.x. Versionsskillnader orsakar kryptiska kompileringsfel som „Symbol ‘serializer’ is missing”. Kontrollera alltid den aktuella versionen på mavenCentral eller i projektets GitHub-repo.
Vanliga frågor
kotlinx.serialization använder kompileringstidskodgenerering via KSP, medan Gson och Moshi använder reflektion under körning. Detta ger en fördel i prestanda (3-5 gånger snabbare än Gson) och typsäkerhet. Gson serialiserar alla fält utan annotering, vilket kan leda till dataläckage. kotlinx.serialization kräver explicit @Serializable-annotering, vilket är säkrare. Moshi stöder också codegen, men endast för JVM och Android.
Ja, kotlinx.serialization är JetBrains officiella multiplattformsbibliotek. Det fungerar på Kotlin/JVM (Android, Backend), Kotlin/Native (iOS), Kotlin/JS (Web, React) och Kotlin/Wasm. API:et är enhetligt för alla plattformar: @Serializable + Json.encodeToString() fungerar likadant överallt. För iOS krävs inga extra inställningar — Kotlin/Native kompilerar serialiserad kod till inbyggd binär.
Nullable-fält (String?) deserialiseras som null om värdet i JSON saknas eller anges som null. För non-nullable-fält (String) utan standardvärde orsakar avsaknad av fält i JSON ett SerializationException. Om du vill att null-värden inte ska komma in i JSON, konfigurera Json { encodeDefaults = false }. Detta kommer att utesluta alla fält som är lika med default (inklusive null för nullable) från utdata.
Använd @SerialName(“snake_case_name”) på varje fält vars namn skiljer sig från Kotlin-formatet. Alternativt, för Kotlin 2.0+ finns Json { namingStrategy = JsonNamingStrategy.SnakeCase } — automatisk konvertering camelCase ↔ snake_case. Denna inställning tillämpas på alla fält samtidigt. Om partiell anpassning behövs, kombinera @SerialName med den globala strategin.
Nej, Flow och korutiner kan inte serialiseras direkt — de representerar asynkron exekvering, inte data. För att överföra data från Flow måste du samla dem i en samling via .toList() i en korutin och serialisera samlingen. På samma sätt kan Job, Deferred eller Continuation inte serialiseras. Serialisera endast data class — datamodeller utan beteendelogik.
Sammanfattning
Vi utvecklar en mobil applikation nyckelfärdigt
IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.
Läs också