Skip to content

feat: SyncJSON — ship the Sendable JSON payload carrier in the library - #672

Merged
3lvis merged 1 commit into
masterfrom
feature/syncjson
Jun 27, 2026
Merged

feat: SyncJSON — ship the Sendable JSON payload carrier in the library#672
3lvis merged 1 commit into
masterfrom
feature/syncjson

Conversation

@3lvis

@3lvis 3lvis commented Jun 26, 2026

Copy link
Copy Markdown
Owner

Why

sync(...) runs off the main actor, so a payload crossing into it must be Sendable. A raw [String: Any] isn't — so any consumer that decodes JSON on one actor and syncs it on another has to hand-roll a Sendable JSON box. The demo had exactly that: DemoSyncPayload + DemoSyncValue, ~120 lines of generic boilerplate every consumer would otherwise reinvent.

This ships that carrier once, in the library.

What

  • SyncJSON — a Sendable enum JSON value conforming to SyncPayloadConvertible, so it feeds sync(payload:) / sync(item:) directly. init(dictionary:), toSyncPayloadDictionary(), and keyed accessors (string(_:), objectArray(_:)). NSNumber is matched first so a JSON boolean isn't read as 1 (the classic bridging trap). Preserves null (so a sync can clear a field) and the value shapes.
  • Demo drops DemoSyncPayload/DemoSyncValue and uses SyncJSON throughout — net deletion of the duplicated box.

Why this one earns its place (unlike the recent detours)

It has a present consumer (the demo adopts it in the same PR) and removes real duplication every consumer would hit — not speculative surface. It's the genuine library-ergonomics gap we traced from the DemoSyncPayload discussion: SwiftSync made consumers BYO-Sendable-JSON; now it doesn't.

Verification

Red-first SyncJSONTests (round-trip, keyed accessors, syncs as a SyncPayloadConvertible). All green: SwiftSync 178, DemoCore 43, DemoBackend 32. README documents the type.

sync(...) runs off the main actor, so a payload crossing into it must be Sendable
— which [String: Any] is not. Consumers were left to hand-roll a Sendable JSON
box (the demo had DemoSyncPayload + DemoSyncValue, ~120 lines). Ship it once in
the library instead:

- SyncJSON: a Sendable enum JSON value conforming to SyncPayloadConvertible, with
  init(dictionary:), toSyncPayloadDictionary(), and keyed accessors (string/objectArray).
  NSNumber matched first so JSON bools aren't read as 1. Preserves null (so a sync
  can clear) and value shapes.
- Demo drops DemoSyncPayload/DemoSyncValue entirely and uses SyncJSON.

red-first: SyncJSONTests (round-trip, accessors, syncs as a payload). All green:
SwiftSync 178, DemoCore 43, DemoBackend 32. README documents it.
@3lvis
3lvis marked this pull request as ready for review June 27, 2026 08:26
@3lvis
3lvis merged commit 2884f38 into master Jun 27, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant