Skip to content

[Protocol] Add support for additional properties#407

Open
markmur wants to merge 4 commits into
mainfrom
protocol-preserve-extensions
Open

[Protocol] Add support for additional properties#407
markmur wants to merge 4 commits into
mainfrom
protocol-preserve-extensions

Conversation

@markmur

@markmur markmur commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

What changes are you making?

This preserves untyped protocol extension properties across generated TypeScript, Swift, and Kotlin models.

What is wrong with the current state?

UCP schemas intentionally allow extension fields through open objects and map-like schemas. The current generated clients do not handle that consistently: Swift and Kotlin decode typed model fields but drop unknown object members, while TypeScript had map-like extension data represented as generated named interfaces such as Signals. That makes current spec fields look more permanent than they are and means future extension keys can disappear or require generator changes when the spec shape changes.

There is also a collision risk when unknown fields are re-encoded. If an extension map contains a key that matches a typed field, the typed field should remain authoritative.

What this fixes

The model generator now derives extension handling from the schema instead of from spec-specific names:

  • open object models get an additionalProperties bucket in Swift and Kotlin
  • map-like schemas using propertyNames plus additionalProperties become native untyped maps in all languages
  • Swift and Kotlin custom encoders re-emit unknown fields, but filter keys that collide with typed fields
  • tests cover preservation of top-level extension keys, nested extension maps, and typed-field collision behavior

Language implications

TypeScript

Open generated models expose unknown fields through additionalProperties, matching Swift and Kotlin. Map-like fields are still emitted inline as { [key: string]: any }. For example, checkout.signals is no longer a generated Signals interface.

import {notificationDescriptors} from "./generated/ProtocolNotifications";

const checkout = notificationDescriptors.complete.decode(params);

const topLevelValue = checkout.additionalProperties?.["com.example.foo"];
const signalValue = checkout.signals?.["com.example.device_id"];

Swift

Open generated models expose unknown fields as additionalProperties: [String: JSONAny]. Map-like fields such as signals are [String: JSONAny]?.

client = client.on(EmbeddedCheckoutProtocol.Event.complete) { checkout in
    let topLevelValue = checkout.additionalProperties["com.example.foo"]?.value as? String
    let signalValue = checkout.signals?["com.example.device_id"]?.value as? String
}

Kotlin

Open generated models expose unknown fields as additionalProperties: Map<String, JsonElement>. Map-like fields such as signals are JsonObject?.

import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.contentOrNull

val checkout = EmbeddedCheckoutProtocol.complete.decode(params) ?: return

val topLevelValue = checkout.additionalProperties["com.example.foo"]
val topLevelString = (topLevelValue as? JsonPrimitive)?.contentOrNull

val signalValue = checkout.signals?.get("com.example.device_id")
val signalString = (signalValue as? JsonPrimitive)?.contentOrNull

@github-actions github-actions Bot added the #gsd:50662 Rebase Checkout Kit on UCP label Jul 6, 2026
@markmur markmur marked this pull request as ready for review July 6, 2026 14:56
@markmur markmur requested a review from a team as a code owner July 6, 2026 14:56
@markmur markmur changed the title Add tests for missing extensions [Protocol] Add support for additional properties Jul 6, 2026
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

React Native — Coverage Report

Lines Statements Branches Functions
Coverage: 92%
91.85% (327/356) 87.98% (183/208) 100% (86/86)

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

Package Size

Platform Artifact Base Head Delta
React Native npm tarball 99.1 KiB 98.9 KiB -203 B
Android release AAR 164.5 KiB 164.5 KiB 0 B
React Native file breakdown
File Base Head Delta
node_modules/@shopify/checkout-kit-protocol/src/generated/Models.ts 85.8 KiB 85.4 KiB -436 B
node_modules/@shopify/checkout-kit-protocol/src/generated/Models.d.ts 53.2 KiB 53.0 KiB -217 B
ios/ShopifyCheckoutKit.swift 15.0 KiB 15.0 KiB 0 B
ios/AcceleratedCheckoutButtons.swift 14.0 KiB 14.0 KiB 0 B
lib/commonjs/index.js 13.1 KiB 13.1 KiB 0 B
src/components/AcceleratedCheckoutButtons.tsx 12.9 KiB 12.9 KiB 0 B
src/index.ts 12.5 KiB 12.5 KiB 0 B
lib/commonjs/components/AcceleratedCheckoutButtons.js 11.4 KiB 11.4 KiB 0 B
android/src/main/java/com/shopify/reactnative/checkoutkit/ShopifyCheckoutKitModule.java 11.1 KiB 11.1 KiB 0 B
lib/module/index.js 10.4 KiB 10.4 KiB 0 B
lib/commonjs/components/AcceleratedCheckoutButtons.js.map 10.3 KiB 10.3 KiB 0 B
lib/module/components/AcceleratedCheckoutButtons.js 10.2 KiB 10.2 KiB 0 B
lib/module/components/AcceleratedCheckoutButtons.js.map 9.1 KiB 9.1 KiB 0 B
src/index.d.ts 8.9 KiB 8.9 KiB 0 B
node_modules/@shopify/checkout-kit-protocol/src/generated/ProtocolNotifications.ts 8.5 KiB 8.5 KiB 0 B
lib/module/index.js.map 8.2 KiB 8.2 KiB 0 B
src/present-dispatcher.ts 8.0 KiB 8.0 KiB 0 B
lib/commonjs/index.js.map 7.7 KiB 7.7 KiB 0 B
lib/commonjs/present-dispatcher.js 6.8 KiB 6.8 KiB 0 B
lib/module/present-dispatcher.js 6.5 KiB 6.5 KiB 0 B
…and 103 smaller files
Android file breakdown
File Base Head Delta
classes.jar 174.1 KiB 174.1 KiB 0 B
res/layout/dialog_checkout.xml 1.3 KiB 1.3 KiB 0 B
proguard.txt 798 B 798 B 0 B
AndroidManifest.xml 578 B 578 B 0 B
res/values/values.xml 564 B 564 B 0 B
R.txt 522 B 522 B 0 B
res/drawable/close.xml 431 B 431 B 0 B
res/menu/checkout_menu.xml 354 B 354 B 0 B
META-INF/com/android/build/gradle/aar-metadata.properties 157 B 157 B 0 B

Measured from the PR base SHA and PR head SHA. The file breakdown shows uncompressed sizes within each package artifact, so individual files do not sum to the compressed artifact total. This comment reports package artifact sizes only; it is not a final app binary-size report.

@markmur markmur force-pushed the protocol-preserve-extensions branch 2 times, most recently from 5d00641 to 7d209cf Compare July 6, 2026 15:54
@markmur markmur force-pushed the protocol-preserve-extensions branch from 7d209cf to dec51dd Compare July 6, 2026 15:56

@kieran-osgood-shopify kieran-osgood-shopify left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything looks good to me

This has me thinking about whether we're approaching the point we might want to consider a fork of quicktype for our purposes, or even some patch-package style changes so we dont have a total fork, to add some flag driven behaviour for us

It might wind up more maintainable than our script which relies on so much string.replace if we can insert some of the behaviour we are inserting here

Comment thread Package.resolved
@markmur markmur force-pushed the protocol-preserve-extensions branch from 0a835e5 to a56fdd7 Compare July 7, 2026 15:50
@markmur

markmur commented Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

This has me thinking about whether we're approaching the point we might want to consider a fork of quicktype for our purposes

Yeah @kieran-osgood-shopify, this is what @westeezy was considering too. I think it's a nice idea to keep the generation logic encapsulated for our purposes and written in a way we know and like.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

#gsd:50662 Rebase Checkout Kit on UCP

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants