Serialization


📝 NOTE: This page is currently out-of-date due to a reason change to the kotlinx.serialization library. The same principals as mentioned here are still true.


Serialization in UCloud is provided by the jackson library. We use the following modules, on top of the core jackson module:

A jackson mapper is available in service-lib and is exported as defaultMapper in the dk.sdu.cloud package. The defaultMapper uses JSON as its data format. This mapper uses the following configuration:

Config Description
DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES = false Don't fail if an ignored property is encountered
DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES = false Don't fail if a new property is introduced in the API
DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY = true Provides flexibility in terms of compatibility
JsonParser.Feature.ALLOW_TRAILING_COMMA = true Provides flexibility in terms of compatibility
JsonParser.Feature.ALLOW_COMMENTS = true Provides flexibility in terms of compatibility

The reason for this configuration is to be as relaxed as possible in order to improve backwards-compatibility.

Sealed Classes

For a variety of reasons, including security, is generally not recommended that you use large class-hierarchies for request/response types. The one exception to this rule is sealed classes. This introduces a problem on the client-side of how to determine the correct type. To solve this problem, you must add annotations to the sealed class which tell Jackson to annotate the resulting JSON with a new key-value pair which include a type-hint.

Examples

Example: Using sealed classes with Jackson

@JsonTypeInfo(
    use = JsonTypeInfo.Id.NAME,
    include = JsonTypeInfo.As.PROPERTY,
    property = TYPE_PROPERTY
)
@JsonSubTypes(
    JsonSubTypes.Type(value = LongRunningResponse.Timeout::class, name = "timeout"),
    JsonSubTypes.Type(value = LongRunningResponse.Result::class, name = "result")
)
sealed class LongRunningResponse<T> {
    data class Timeout<T>(
        val why: String = "The operation has timed out and will continue in the background"
    ) : LongRunningResponse<T>()

    data class Result<T>(
        val item: T
    ) : LongRunningResponse<T>()
}

Example: Using the defaultMapper instance to parse a JSON object

val result = defaultMapper.readValue<Page<Tool>>(jsonText)

Example: Using the defaultMapper instance to serialize an object to JSON

defaultMapper.writeValueAsString(SlackMessage(message))