AtField
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 itPresent 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.