Using kotlinx.serialization to deserialize a JSON array into a sealed class

Stored as nested JSON arrays, my data is in rich text format. The plaintext of the string and annotations describing the formatting are stored in text tokens. At decode time, I aim to map the specific structure of these nested JSON arrays to a rich Kotlin class hierarchy.

The TypeScript type below describes this text encoding:

// Text string is an array of tokens
type Text = Array<TextToken>
// Each token is a Array[2] tuple. The first element is the plaintext.
// The second element is an array of annotations that format the text.
type TextToken = [string, Array<Annotation>]
// My question is about how to serialize/deserialize the Annotation type
// to a sealed class hierarchy.
//
// Annotations are an array where the first element is always a type discriminator string
// Each annotation type may have more elements, depending on the annotation type.
type Annotation =
 | ["b"] // Text with this annotation is bold
 | ["i"] // Text with this annotation is italic
 | ["@", number] // User mention
 | ["r", { timestamp: string, reminder: string }] // Reminder

To represent the same thing using Kotlin classes with sealed class, I have defined the following structured output format after deserializing the JSON:

// As JSON example: [["hello ", []], ["Jake", [["b"], ["@", 1245]]]]
data class TextValue(val tokens: List<TextToken>)

// As JSON example: ["hello ", []]
// As JSON example: ["Jake", [["b"], ["@", 1245]]]
data class TextToken(val plaintext: String, val annotations: List<Annotation>)

sealed class Annotation {
  // As JSON example: ["b"]
  @SerialName("b")
  object Bold : Annotation()

  // As JSON example: ["i"]
  @SerialName("i")
  object Italic : Annotation()

  // As JSON example: ["@", 452534]
  @SerialName("@")
  data class Mention(val userId: Int)

  // As JSON example: ["r", { "timestamp": "12:45pm", "reminder": "Walk dog" }]
  @SerialName("r")
  data class Reminder(val value: ReminderValue)
}

How should I define my serializers? I attempted defining a serializer using JsonTransformingSerializer, but encountered a null pointer exception when trying to wrap the default serializer for one of my classes:

@Serializable(with = TextValueSerializer::class)
data class TextValue(val tokens: List<TextToken>)

object TextValueSerializer : JsonTransformingSerializer<TextValue>(TextValue.serializer()) {
    override fun transformDeserialize(element: JsonElement): JsonElement {
        return JsonObject(mapOf("tokens" to element))
    }

    override fun transformSerialize(element: JsonElement): JsonElement {
        return (element as JsonObject)["tokens"]!!
    }
}
Caused by: java.lang.NullPointerException: Parameter specified as non-null is null: method kotlinx.serialization.json.JsonTransformingSerializer.<init>, parameter tSerializer
    at kotlinx.serialization.json.JsonTransformingSerializer.<init>(JsonTransformingSerializer.kt)
    at example.TextValueSerializer.<init>(TextValue.kt:17)

Answer №1

The issue you are experiencing appears to be due to referencing the TextValue serializer within the TextValue serializer.

Because the data structure does not align perfectly with the key:value pairings expected by the serializer, it becomes more challenging to automate this process.

For your current setup, here is what you will need to do, starting from the bottom up:

  1. Annotation

    You can create a custom serializer that converts the representation of JsonArray to its corresponding Annotation format. This involves mapping the indices of the JsonArray to their respective sealed class representations. By using the first index as the discriminator, we can determine the type we are mapping to.

    In cases where possible, utilizing the auto-generated serializers is recommended.

    []          -> Annotation.None
    ["b"]       -> Annotation.Bold
    ["@", 1245] -> Annotation.Mention
    ...
    

    To achieve this, create a new serializer and associate it with the Annotation class (

    @Serializable(with = AnnotationSerializer::class)
    ).

    object AnnotationSerializer : KSerializer<Annotation> {
        // Implementation details for serializing and deserializing Annotations
    }
    
  2. TextToken

    The approach for handling TextToken mirrors that of Annotation. Extracting the token at the first index and building the annotations using the second index. Annotate the TextToken class to use the following serializer:

    object TextTokenSerializer : KSerializer<TextToken> {
        // Implementation details for serializing and deserializing TextToken
    }
    

    Consider returning JSON like

    { plaintext: "Jake", annotations: [["b"], ["@", 1245]] }
    , which aligns better with the TextToken POJO and avoids the need for such serialization.

  3. TextValue

    The final component is the TextValue object, which essentially wraps the list of TextTokens. It may be beneficial to use a type alias for this purpose:

    typealias TextValue = List<TextToken>
    

    One option is to utilize a serializer that translates the JsonArray into the List<TextToken> and then encompasses that list in the TextValue object.

    object TextValueSerializer : KSerializer<TextValue> {
        // Implementation details for serializing and deserializing TextValue
    }
    

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Utilizing files that do not have the extension '.ts' or '.tsx' within the 'ts_library' as dependencies

My current challenge involves importing a JSON file from TypeScript while utilizing the resolveJsonModule flag in my tsconfig. The problem lies in how I can provide this JSON file to ts_library since it seems unable to locate the file. This issue extends t ...

How can Python be used to export hierarchical JSON data into an Excel xls file?

Looking to transfer data from Python to xlsx format. Currently have the data stored in JSON format. It doesn't matter what type of data is being exported out of Python. Here's an example JSON structure for a single article: { 'Word Coun ...

Struts2 jQuery tag select fails to fetch data

I've been attempting to utilize the <sj:select> tag to display my list in Struts2 JSP. Within the same section, I have also included a <s:select> tag The data is being populated in the <s:select> tag while no data is showing up in ...

Tips for implementing collapsible functionality that activates only when a specific row is clicked

I need to update the functionality so that when I click on a row icon, only that specific row collapses and displays its data. Currently, when I click on any row in the table, it expands all rows to display their content. {data.map((dataRow ...

The REST Client is reporting back with an HTTP status code of 401

Thank you for taking the time to read this! Overview: I have developed a JAVA REST client that authenticates with a username and password, returning a JSON response. Issue: I am encountering the following exception: Error in thread "main" java.io.IOExc ...

Is it possible to maintain component data in Angular while also incorporating dynamic components?

All the code you need can be found here: https://stackblitz.com/edit/angular-keep-alive-component?file=src/app/app.component.ts Is it possible to maintain the state of entered values when switching components? I am currently utilizing dynamic component r ...

Manipulate JSON data in a Node.js loop

Currently, I am working on a monitoring system that will indicate on a website whether a server is up or down. I have experimented with various methods such as regex and replacement to modify the JSON file. My main objective is to dynamically change the "s ...

RSPEC failing to automatically invoke 'to_json' on 'render(json: @instance)'

While testing a controller action in rspec, we encountered an unexpected issue with the json response. Instead of receiving a full json object of the instance as expected, we were getting a partial response. => render(json: @instance) "{\" ...

What is the best way to set up environments for Google App Engine (GAE

After developing a web app with server and client components, I decided to deploy it to Google Cloud using App Engine. Although the deployment process was successful, the main issue lies in the non-functioning env_variables that are crucial for the applic ...

Using a static class reference as a parameter in a generic type leads to a error

Before I submit a ticket on github, I want to double-check that I'm not making any mistakes. The issue should be clear enough: class A {} class B { static A = A; } function foo<T>(arg: T) {} // this is valid const b = new B.A; // "B" only ...

Combining numerous interfaces into a unified interface in Typescript

I'm struggling to comprehend interfaces in Typescript, as I am facing difficulty in getting them to function according to my requirements. interface RequestData { [key: string]: number | string | File; } function makeRequest(data: RequestData) { ...

Output JSON data from PHP for use in Javascript

Is there a way to effectively convert JSON data from PHP/Laravel into JSON for JavaScript? I have the JSON string from PHP, but it is only rendering as a string. How can I convert it to a JSON object in JavaScript? Take a look at my code below. $('#e ...

ToolBox Item Not Found

It appears that the JSON Encoder and Decoder are missing from my Visual Studio 2013 environment. I have BizTalk 2013 R2 installed on my development VM along with Visual Studio 2013 Premium. Interestingly, a colleague of mine has access to the JSON Encoder/ ...

Fast screening should enhance the quality of the filter options

Looking to enhance the custom filters for a basic list in react-admin, my current setup includes: const ClientListsFilter = (props: FilterProps): JSX.Element => { return ( <Filter {...props}> <TextInput label="First Name" ...

Arranging JSON data in Python based on string values for sorting

I have a JSON file containing employee information: ll = {"employees":[ {"firstName":"John", "lastName":"Doe"}, {"firstName":"Anna", "lastName":"Smith"}, {"firstName":"James", "lastName":"Bond"}, {"firstName":"Celestial", "lastName":"Syste ...

Implementing dynamic display of div based on dropdown selection in typescript

A solution is needed to display or hide specific div elements based on a dropdown selection using Typescript. Sample HTML file: <select class="browser-default custom-select"> <option selected>single</option> <option value="1"> ...

Implementing Entity addition to a Data Source post initialization in TypeORM

The original entity is defined as shown below: import { Entity, PrimaryGeneratedColumn} from "typeorm" @Entity() export class Product { @PrimaryGeneratedColumn() id: number The DataSource is initialized with the following code: import ...

JSX tags without any inner content should be self-closed

After successfully running this code, I encountered an issue when committing it to git. The error message 'ERROR: src/layouts/index.tsx:25:9 - JSX elements with no children must be self-closing' appeared. I attempted to resolve the error by addi ...

Error with JSON parsing in JavaScript when processing large numbers

After requesting a list of approved tweets from a webserver in JSON format, I visited the URL: http://localhost:8000/showtweets/?after_id=354210796420608003 and received the following JSON response: [{ "date": "2013-07-08T12:10:09", "text": "#R ...

Tips for Logging HTTP Communication Errors in Angular

When making an HTTP put call to update a record in my .Net MVC application, I have noticed that the controller's put logic is not being triggered as expected compared to other types of HTTP requests. I want to implement error handling by using the Ha ...