- Serialization
- Libraries
- Formats
- Example: JSON serialization
- Как и для чего использовать нативную библиотеку сериализации Kotlin
- Вывод из статьи
- Почему команда Kotlin создала новую библиотеку сериализации
- Интеграция
- Как использовать сериализацию Kotlin с классами данных
- Retrofit 2 с kotlinx-сериализацией
- Cкрытые возможности
- Безопасность во время компиляции
- Переходные и необязательные аннотации
- Kotlin: Serialization
Serialization
Serialization is the process of converting data used by an application to a format that can be transferred over a network or stored in a database or a file. In turn, deserialization is the opposite process of reading data from an external source and converting it into a runtime object. Together they are an essential part of most applications that exchange data with third parties.
Some data serialization formats, such as JSON and protocol buffers are particularly common. Being language-neutral and platform-neutral, they enable data exchange between systems written in any modern language.
In Kotlin, data serialization tools are available in a separate component, kotlinx.serialization. It consists of two main parts: the Gradle plugin – org.jetbrains.kotlin.plugin.serialization and the runtime libraries.
Libraries
kotlinx.serialization provides sets of libraries for all supported platforms – JVM, JavaScript, Native – and for various serialization formats – JSON, CBOR, protocol buffers, and others. You can find the complete list of supported serialization formats below.
All Kotlin serialization libraries belong to the org.jetbrains.kotlinx: group. Their names start with kotlinx-serialization- and have suffixes that reflect the serialization format. Examples:
org.jetbrains.kotlinx:kotlinx-serialization-json provides JSON serialization for Kotlin projects.
org.jetbrains.kotlinx:kotlinx-serialization-cbor provides CBOR serialization.
Platform-specific artifacts are handled automatically; you don’t need to add them manually. Use the same dependencies in JVM, JS, Native, and multiplatform projects.
Note that the kotlinx.serialization libraries use their own versioning structure, which doesn’t match Kotlin’s versioning. Check out the releases on GitHub to find the latest versions.
Formats
kotlinx.serialization includes libraries for various serialization formats:
Note that all libraries except JSON serialization ( kotlinx-serialization-core ) are Experimental, which means their API can be changed without notice.
There are also community-maintained libraries that support more serialization formats, such as YAML or Apache Avro. For detailed information about available serialization formats, see the kotlinx.serialization documentation.
Example: JSON serialization
Let’s take a look at how to serialize Kotlin objects into JSON.
Before starting, you’ll need to configure your build script so that you can use Kotlin serialization tools in your project:
Apply the Kotlin serialization Gradle plugin org.jetbrains.kotlin.plugin.serialization (or kotlin(“plugin.serialization”) in the Kotlin Gradle DSL).
Add the JSON serialization library dependency: org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.1
Now you’re ready to use the serialization API in your code. The API is located in the the kotlinx.serialization package and its format-specific subpackages such as kotlinx.serialization.json .
First, make a class serializable by annotating it with @Serializable .
You can now serialize an instance of this class by calling Json.encodeToString() .
As a result, you get a string containing the state of this object in the JSON format:
You can also serialize object collections, such as lists, in a single call.
To deserialize an object from JSON, use the decodeFromString() function:
For more information about serialization in Kotlin, see the Kotlin Serialization Guide.
Источник
Как и для чего использовать нативную библиотеку сериализации Kotlin
Apr 28, 2020 · 4 min read
Вывод из статьи
В конце статьи вы узнаете, почему команда Kotlin создала новую библиотеку сериализации, несмотря на наличие многих продвинутых решений, таких как Moshi и Gson. Также вы узнаете, как ей правильно пользоваться, а в конце статьи ознакомитесь со скрытыми функциями встроенной сериализации.
Почему команда Kotlin создала новую библиотеку сериализации
Есть много известных и эффективных библиотек сериализации, таких как Moshi от Square и Gson от Google, но команда Kotlin решила создать совершенно новую собственную библиотеку сериализации для Kotlin. Вопрос — почему?
Библиотеки сериализации, таки е как Moshi и Gson, являются Java-библиотеками, использующими рефлексии, что прекрасно подходит для Android разработки. Kotlin не ограничивается Android (JVM). Он поддерживает JavaScript и IOS (native) разработки. Рефлексии точно не сработают с Kotlin.js и нативными модулями. Кроме того, их использование в Android является недостатком.
Помимо мультиплатформенной поддержки и рефлексий в сериализации, существует еще один недостаток библиотек сериализации Java: они не поддерживают переменные значения в Kotlin по умолчанию. Для понимания, давайте начнем с простого класса данных, как показано ниже:
Когда мы пытаемся спарсить JSON только с узлом data , то значение optionalData изменяется на null вместо присвоения значения по умолчанию empty , объявленного в классе данных. Это большая проблема, потому что, когда переменная объявляется без вопросительного знака, компилятор Kotlin гарантирует, что переменная никогда не будет null , но обычные библиотеки сериализации Java об этом не знают. Этот тип функционального конфликта приводит к неожиданному поведению приложения.
Поэтому команда Kotlin решила создать библиотеку сериализации, которая работает со всеми платформами, поддерживающими этот язык, а также не имеет рефлексий.
Интеграция
Чтобы использовать библиотеку сериализации Kotlin, мы должны интегрировать как плагин сериализации, так и библиотеку среды выполнения. Плагин сериализации генерирует код для парсинга JSON без использования каких-либо рефлексий. Также библиотека среды выполнения использует этот код для сериализации классов данных.
Чтобы интегрировать модуль сериализации, добавьте следующую строку под всеми плагинами в первых строках файла build.gradle уровня приложения.
Также нужно добавить строку ниже под узлом зависимостей в файле build.gradle уровня проекта:
Затем добавьте следующую реализацию библиотеки под меткой зависимостей в файле build.gradle уровня приложения:
Как использовать сериализацию Kotlin с классами данных
С помощью этой нативной библиотеки от команды Kotlin сериализация может быть выполнена довольно быстро. Нам нужно добавить аннотацию @Serializable над предполагаемым классом. Обратите внимание:
Вот и все. Вам не нужно аннотировать каждую переменную с помощью сериализуемых меток, как это делается в обычных библиотеках.
Теперь давайте рассмотрим более реалистичный пример с Retrofit 2.
Retrofit 2 с kotlinx-сериализацией
Мы все знакомы с адаптерами retrofit для RxJava, Coroutines, Moshi и других библиотек. Аналогичным образом сериализация Kotlin так же имеет адаптер от JakeWharton, через который мы можем связать ответ retrofit с кодом сериализации Kotlin.
Чтобы интегрировать эту библиотеку в свой проект, добавьте строку ниже под узлом dependencies в файле build.gradle уровня приложения:
Теперь пришло время связать адаптер с инстансом retrofit. Взгляните на код ниже:
Предполагая, что результирующий класс данных аннотируется меткой @Serializable , это все, что нам нужно сделать. Об остальном позаботится Kotlin Serialization Converter.
Cкрытые возможности
Безопасность во время компиляции
Это необходимо, когда вы выполняете некоторую сложную сериализацию, например, при вложении классов в класс данных ответа. Обратите внимание:
Хорошо, что вы аннотировали класс данных SimpleExample с помощью @Serializable , но что делать, если забыли аннотировать ComplexClass ?Может ли это вызвать сбой во время выполнения? Или вы сами проверили, является ли каждый вложенный класс аннотированным или нет?
Расслабьтесь. Ни одна из вышеперечисленных ошибок не произойдет, потому что библиотека сериализации Kotlin безопасна во время компиляции, а это означает, что она показывает ошибку, если вы не аннотировали ни один из вложенных классов с помощью @Serializable , независимо от того, насколько глубока структура дерева.
Переходные и необязательные аннотации
Переходные: аннотируя переменную в классе данных как @Transient , мы говорим сериализатору, чтобы он полностью игнорировал это поле.
Необязательные: аннотируя переменную в классе данных как @Optional , мы указываем сериализатору, что она необязательна, поэтому не прерывайте процесс, если вы не нашли ее в ответе. Кроме того, мы можем назначить значение по умолчанию, как показано ниже.
Источник
Kotlin: Serialization
After chatting with Sebastiano Poggi about the issues with using Kotlin objects that I covered in a recent post, he made the interesting suggestion that possibly kotlinx.serialization might work better than the Java implementation because it is faster, more flexible (we can use different serialisation formats such as JSON or Protobuf), and is built with knowledge of the Kotlin language, so will possibly handle the singleton implementation of Kotlin objects better. This post covers some of the basics of Kotlin serialisation, and what was learned along the way.
As the name suggests, kotlinx.serialization is an equivalent to Java serialisation and there is much to like. One thing worth mentioning is that it is still very much a work in progress; It is still experimental and, as we shall see, is far from complete. At the time of writing (November 2019) I do not realistically see it hitting full release in the immediate future.
Let’s start by setting up our project to use kotlinx.serialization:
I felt that the best way of checking it was to apply it to the same class hierarchy as in the previous article:
One of the fundamental principles of kotlinx.serialization is that we should be able to tag classes as being Serializable using the @Serializable annotation and the necessary serialisation code will be generated at compile time and will avoid the runtime performance issues of reflection-based approaches. So the overhead compared to Java serialisation is pretty minimal.
IMPORTANT UPDATE: The following is no longer true. With Kotlin 1.3.60+ and kotlinx.serialization 0.14.0+ Kotlin object serialisation is now supported. More details can be found here. Although the code for the rest of this article in no longer necessary for Kotlin object serialisation, it may be useful to those who need to write custom serialisers, so I have decided to leave it in place.
However, at the time of writing, Kotlin objects are not actually supported yet, and if we try this we’ll get compiler errors on Object and Object2 :
This has changed – if you see this error then you should check that you’re using Kotlin 1.3.60 or later and kotlinx.serialization 0.14.0 or later.
I must confess that, at this point, I almost abandoned this idea because it felt like it would be better to wait until kotlinx.serialization fully supports Kotlin object serialisation. However, my curiosity wanted to understand what was necessary to actually get this working. While what follows will be obsolete once kotlinx.serialization fully supports Kotlin object serialisation, some of the principles are still quite useful – particularly when it comes to writing customer serializers , so there is still value in covering this.
It is worth mentioning that once it reaches a full release kotlinx.serialization will probably be as simple to implement as the above code (with perhaps some small changes), it’s just not there yet.
I had an initial stab at writing a custom serializer for Kotlin objects and it seemed like quite a lot of code. I decided to check with Eugenio Marletti who is a Developer Advocate at JetBrains whose knowledge of Kotlin is far more nuanced than mine. Eugenio confirmed that my technique was correct, but offered some great suggestions of how the code could be improved and compacted. The code that follows is basically my initially verbose code which has been cleaned up / improved by Eugenio.
Let’s start by looking at the enabler code. While there is only 12 lines of code here, there’s an awful lot going on:
The ObjectSerializer interface is a generic extension of KSerializer (part of kotlinx.serialization) which adds a serializer() function. The value this gives us should be a little clearer once we see how it is implemented.
The createObjectSerializer is where a lot happens. This is the place where we do the main work required by KSerializer .
The descriptor is used as a key to this KSerializer implementation and needs to clearly identify that class that this KSerializer instance is responsible for. This is what the framework uses to find the correct KSerializer to use during deserialisation.
The deserialize() method defers the object deserialisation to the instance object passed in. This is actually doing the equivalent of the readResolve() implementation that we needed to do for Java serialisation in the previous article – it will always return instance when an object of this type is deserialised.
The serialise() method is responsible to writing the object – beginStructure() and endStructure() can be envisaged as XML:
The key thing here is the descriptor being written which enables the same deserializer to be called.
With these two enablers we can now update any / all objects within out project. In this case we update our class hierarchy:
The @Serializable annotation accepts an optional with attribute which allows us to specify a custom serialiser which implements a method named serializer() . This is where the ObjectSerializer interface should make sense – it matches that signature.
Each object then implements ObjectSerializer and delegates to an instance returned by createObjectSerializer() . For the instance argument it passes in itself – the singleton that needs to be be deserialised. This is the other side of the readResolve() equivalence we saw earlier.
It is actually worth noting that we don’t have to do anything for DataClass as this is fully supported when it contains only primitive types.
We’re not quite there yet because kotlinx.serialization does not generate the necessary code around sealed classes, but does have polymorphism support and we can expose what is necessary through that. For this we need to expose a couple of statics though a companion object:
serializer exposes is a KSerialiser interface that is actually a PolymorphicSerializer implementation.
serializersModule exposes the polymorphic instance serialisers for subclasses of KSerializerSealedClass .
With these we can serialise and deserialise any subclass of KSerializerSealedClass using 3 lines of code:
This is not something you would want to do in production code: serialising and immediately deserialising makes no sense whatsoever. But it makes sense in test code, and this was taken from the unit tests that are in the sample project.
We first create a serializer – in this case a JSON serialiser. This gets the serializersModule that we just defined – so it knows there to defer serialisation of subclasses of KSerializerSealedClass .
We can now serialise the object to JSON by calling stringify() on the serializer we just created, and passing in the serializer that we exposed through the KSerializerSealedClass companion object.
We can then deserialise using the same SerializerSealedClass.serializer and the JSON output.
This does show one of the advantages of kotlinx.serialization – the custom serialization code is totally agnostic of the format being serialized to (in this case JSON). At present JSON, Cbor, and Protobuf are supported, but there are also additional plugins (some community contributions) for HOCON, Avro, Bson, XML, and YAML formats. These can be interchanged depending on requirements without requiring changes to the custom serializers that we created earlier for our Kotlin objects.
If we compare the final sealed class with the Java Serializeable equivalent, it should be quite obvious that this is far more verbose, and that it simply because we are having to manage the Kotlin object serialisation ourselves rather than kotlinx.serialization doing that for us. Moreover we are actually having to do the equivalent of implementing readResolve() in Java Serializable – the lambda function we pass in to createObjectSerializer() returns the singleton instance of the object, and this lambda is called by the deserialize() method of the ObjectSerializer object that we create for each Kotlin object. If we implement readResolve() we should return the singleton instance, and we are effectively doing exactly the same thing here whenever the Kotlin object is deserialised.
That said, there’s still a lot to like about kotlinx.serialization and once it becomes more feature complete it will be a really nice, flexible, performant alternative to Java Serializable with the added advantage of understanding the design patterns that Kotlin uses upon the JVM and being able to correctly apply them during serialization. Once we get close to a 1.0 release I’m hopeful that the implementation will be as simple as adding the @Serializable annotation as we did in the first example which came with a warning that it does not work. Yet! – the importance of that final word should now be a little clearer. There may be slight differenced in syntax, but that’s about the level of complexity and effort that we should be looking at.
Once again my huge thanks to Seb, for planting the seed for this article, and to Eugenio for performing his Kotlin magician skills on my code.
The source code for this article is available here.
Источник