AtField

sealed interface AtField<out T>

Three-state optional field for AT Protocol mutation payloads.

The AT Protocol wire format distinguishes three states for an optional field:

  • Absent from the JSON object entirely — caller didn't touch it

  • Present with value null — caller explicitly cleared it

  • Present with a concrete value — caller set it

This distinction matters for mutations like app.bsky.actor.putPreferences where "leave unchanged" and "clear this field" are different operations. A plain T? = null collapses the first two states, making the distinction unrepresentable — see the design doc for the full rationale.

Usage on a generated data class:

@Serializable
data class PutPreferencesInput(
val did: Did,
@EncodeDefault(EncodeDefault.Mode.NEVER)
val displayName: AtField<String> = AtField.Missing,
)

The = AtField.Missing default is what makes the absent-key → Missing decode path work (kotlinx-serialization only invokes the field serializer when the key is present). The @EncodeDefault(NEVER) is what makes the encode path skip the field when it equals Missing. Both annotations are required when encodeDefaults = true; see AtFieldSerializer.serialize for the fail-loud diagnostic if they're missing.

The runtime Json configuration MUST keep explicitNulls = true (the default). Setting it to false would silently collapse Null into Missing on the wire and defeat the whole design.

Inheritors

Types

Link copied to clipboard
data class Defined<out T>(val value: T) : AtField<T>

The field key is present with a concrete value.

Link copied to clipboard
data object Missing : AtField<Nothing>

The field key is absent from the JSON object.

Link copied to clipboard
data object Null : AtField<Nothing>

The field key is present with an explicit JSON null value.