Skip to content

refactor!: migrate totals.json from array to object schema - #684

Draft
gsmith85 wants to merge 2 commits into
mainfrom
feat/refactor-totals-object-schema
Draft

refactor!: migrate totals.json from array to object schema#684
gsmith85 wants to merge 2 commits into
mainfrom
feat/refactor-totals-object-schema

Conversation

@gsmith85

@gsmith85 gsmith85 commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Summary

While auditing downstream code generator workarounds during review of RFC #484, I identified that source/schemas/shopping/types/totals.json uses a RootArray ("type": "array") schema which is responsible for much of the post processing logic our SDK generation requires.

This PR refactors totals.json from a top-level array into a top-level object ("type": "object") which will:

  1. Eliminate custom post-processing handling for the current schema in our *-sdk repositories.
  2. Align the totals.json schema with modern UCP schema design guidelines.
  3. Provide a more flexible API for extension in the shopping space.

Why RootArray Schemas Cause Generator Friction

Defining a resource as a RootArray ("type": "array" at schema root) causes four specific failure modes in schema-driven toolchains:

  • Loss of Named Class Emission (RootModel Friction): Generators cannot emit standard named classes (class Totals(BaseModel)). Instead, they emit anonymous RootModel / TypeAliasType wrappers, breaking direct JSON serialization in web frameworks (e.g., FastAPI).
  • Access Friction: Forces developers to write array searches (totals.find(t => t.type === "subtotal")) instead of direct typed property access (totals.subtotal.amount).
  • Dropped Validation & Maintenance Hacks: Code generators drop array contains/minContains rules entirely, forcing 180+ lines of AST post-processing in Python (postprocess_models.py#L170-L346) and 8 duplicated .superRefine Zod callbacks in TypeScript (spec_generated.ts#L677-L700).
  • Extensibility Deficit: An array cannot accept top-level metadata (like a display_order hint) in future revisions without a breaking schema change.

Developer Experience Comparison

Python SDK:

# BEFORE (clunky array search / RootModel):
subtotal = next(t.amount for t in checkout.totals if t.type == "subtotal")

# AFTER (strongly-typed property access):
subtotal = checkout.totals.subtotal.amount

TypeScript SDK:

// BEFORE (array find + optional chaining / non-null assertion):
const subtotal = checkout.totals.find(t => t.type === "subtotal")!.amount;

// AFTER (direct autocompleted property access):
const subtotal = checkout.totals.subtotal.amount;

Conformance with UCP Schema Guidelines

The refactored totals.json and total.json schemas directly conform to official UCP authoring standards:

  1. Open String Vocabularies over Enums:
    • Guideline: docs/documentation/schema-authoring.md#string-vocabularies-vs-enums ("Prefer open string vocabularies with documented well-known values over closed enum arrays.")
    • Impact: Removes the legacy conditional "enum": ["subtotal", "tax", ...] array from totals.json, preventing breaking changes when new total categories are added in future releases.
  2. Avoiding Complex Conditional Validation:
    • Guideline: docs/documentation/schema-authoring.md#open-enumerations ("Avoid complex validation patterns... as they frequently confuse client-side code generators and make schemas difficult to read.")
    • Impact: Replaces brittle array contains / allOf / if/then conditional rules with standard object properties (required: ["subtotal", "total"]), eliminating generator confusion.
  3. Consistency Across Protocol Resources:
    • Guideline: docs/specification/overview.md
    • Impact: Brings totals.json into 100% architectural parity with all other core UCP resources (cart, checkout, payment, fulfillment), which are uniformly modeled as top-level objects.

Receipt Rendering & Itemization

  • Display Sequence: The optional display_order string array hint (["subtotal", "discount", "fulfillment", "tax", "total"]) preserves custom merchant receipt rendering order without requiring array schema hacks.
  • Sub-Line Breakdown: Itemized sub-lines (e.g. multi-jurisdiction taxes) are preserved via the nested lines array in total.json.
  • Shopping Space Extensibility: Uses open string properties backed by additionalProperties: { "$ref": "total.json" }. This allows businesses and platform extensions to introduce shopping line items (gift_wrapping_fee, recycling_fee, bottle_deposit, tip, store_credit) without requiring a breaking schema revision or version bump.

@gsmith85
gsmith85 force-pushed the feat/refactor-totals-object-schema branch from adb3773 to 0ee0023 Compare August 5, 2026 04:48
@gsmith85
gsmith85 force-pushed the feat/refactor-totals-object-schema branch from 0ee0023 to 051e2db Compare August 5, 2026 04:53
@gsmith85 gsmith85 changed the title refactor!: migrate totals.json from RootArray to object schema refactor!: migrate totals.json from array to object schema Aug 5, 2026
@gsmith85
gsmith85 requested review from amithanda and damaz91 August 5, 2026 18:23
@damaz91 damaz91 added the schema PR changing the UCP schema label Aug 10, 2026
"amount"
],
"properties": {
"type": {

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.

probably we should also remove this, as type is now defined as the key in the totals dictionary?

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

Labels

area:payments schema PR changing the UCP schema

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants