Read this when you need to know exactly how SwiftSync interprets payload keys, null, relationships, and export keys.
If you only need the practical rule set, start here:
- convention-first mapping is the default
- missing key means ignore
- explicit
nullmeans clear @RemoteKeyand@PrimaryKey(remote:)override conventions
This document is the source of truth for the detailed behavior behind those rules.
This contract defines current behavior for:
- inbound key resolution
- null/missing semantics
- relationship key conventions
- container-level input key style
- export key emission contract
- scalar type coercion policy
- Convention-first mapping is expected.
absent = ignorefor payload fields.null = clearfor optional fields/relationships when delete semantics apply.- Explicit overrides (
@PrimaryKey(remote:),@RemoteKey) take precedence over conventions.
Practical defaults:
- Prefer convention mapping first; add
@RemoteKeyonly when local naming intentionally differs. - Prefer
projectID,remoteURL,uuidValuestyle names; SwiftSync normalizes acronyms for snake/camel mapping. - Configure inbound key style once at
SyncContainer(.snakeCasedefault,.camelCaseoptional).
Inbound key style is configured at SyncContainer:
.snakeCase(default).camelCase
The configured style is applied to all payload lookups performed during sync.
For generated model input keys:
@PrimaryKey(remote:)if present@RemoteKeyif present- property name by convention
Lookup behavior in SyncPayload:
- style-transformed candidate (based on container
keyStyle) - literal requested key
- identity aliases (
id/remote_id/remoteIDas applicable)
Example:
- Property
projectID .snakeCasemode acceptsproject_id.camelCasemode acceptsprojectId
Relationship lookup uses the same SyncPayload candidate pipeline.
Foreign-key conventions:
- to-one:
<relationship>_id - to-many IDs:
<relationship>_idsand singularized fallback (watcher_idsforwatchers) @RemoteKeyoverrides FK convention
Nested-object conventions:
- relationship name by convention
@RemoteKeymay target nested key paths using dotted notation
Dispatch order for relationships:
- if FK key is present, process FK path
- else if nested key is present, process nested object path
Deep-path behavior:
- dotted keys (for example
relationships.owner) are resolved against nested dictionaries on import. - deep-path lookup uses the same key-style candidate pipeline (
snakeCase/camelCase) as flat keys. - nested relationship payload processing keeps the parent sync key-style configuration.
- Missing key: no change.
- Explicit
NSNull: - optional scalar: clears to
nil - non-optional scalar: resets to default fallback in required reads
- optional relationship: clears when delete operation is enabled
- non-optional relationship: cannot be cleared
Relationship operation flags still gate insert/update/delete behavior.
- FK parsing is strict for relationship IDs.
- Unknown FK references are soft no-ops (no placeholder creation, no forced clear).
For @Syncable models, SwiftSync emits compile-time diagnostics for blocked names:
description(suggested replacement:descriptionText)hashValue(suggested replacement:hashValueRaw)
When payload keys still use blocked names, map them with @RemoteKey.
Export key precedence:
@RemoteKey@PrimaryKey(remote:)- convention transform from export
keyStyle
Defaults:
- export key style defaults to snake_case
- relationship export mode defaults to array mode
Round-trip expectation:
- if import/export use matching conventions and no divergent overrides, exported keys match expected API keys.
Supported scalar coercions on inbound attribute reads include:
- string -> numeric (
Int,Double,Float,Decimal) when parseable - integer/number ->
Double,Float,Decimal - number ->
Int(truncating numeric conversion) - string ->
Bool(true/false,1/0,yes/no) - numeric
0/1->Bool - string ->
UUID - string ->
URL UUID/URL/numeric/bool/decimal ->String- string/number ->
Datevia parser and unix timestamp handling
Strict reads (strictValue, relationship FK linking) remain non-coercive by design.