Skip to content

CONCEPT Cited by 1 source

JSON-string parameters for schema stability

Definition

JSON-string parameters for schema stability is the design choice to carry element-specific content as an opaque JSON string inside a stable, minimal outer schema, rather than encoding every element variant as a distinct typed case in the schema.

In Yelp's CHAOS GraphQL schema, every component is a ChaosJsonComponent with three visible fields: identifier, componentType (e.g. chaos.button.v1), and parameters — a string that the backend populates with escaped JSON and the client parses into the element-specific shape.

{
  "__typename": "ChaosJsonComponent",
  "identifier": "find-local-businesses-button",
  "componentType": "chaos.button.v1",
  "parameters": "{\"text\": \"Find local businesses\", \"style\": \"primary\", \"onClick\": [\"open-search-url\"]}"
}

Verbatim rationale from the 2025-07-08 post: "Instead of defining individual schemas for each element in the GraphQL layer, we use JSON strings for element content. This approach maintains a stable GraphQL schema and allows for rapid iteration on new elements or versions."

Why it's a real tradeoff, not just laziness

Benefits of opaque-string parameters:

  • No schema PR for new elements. Adding chaos.newcomponent.v1 requires a backend dataclass and a client renderer — no GraphQL schema change, no supergraph redeployment.
  • Versioning is in-band via the type string. chaos.text.v1 vs chaos.text.v2 can coexist in the same response; clients pick the version they support.
  • Federated schema churn stays low. In an Apollo Federation supergraph, every schema change ripples through the router and tooling. Keeping the CHAOS schema stable means the CHAOS team can iterate on elements without coordinating with the rest of the supergraph.

Costs you're explicitly accepting:

  • No GraphQL-level validation of element content. The GraphQL introspection type is String, not ChaosButtonV1Parameters. Clients get no schema-driven type safety for parameters.
  • Tooling degrades. GraphQL codegen tools can't generate typed accessors for parameters; each platform's client writes a JSON parser and a switch on componentType.
  • Schema drift becomes a correctness problem. The backend's Python dataclass (TextV1) and each client's renderer must agree on the parameter shape — via docs, code review, or a separate schema registry — not via GraphQL.
  • Discoverability suffers. A new engineer looking at the CHAOS schema sees one parameters: String field and has to go elsewhere to learn the actual element vocabulary.

When to use this pattern

  • Rapid-iteration UI systems where the element vocabulary changes faster than the API schema can safely evolve. SDUI frameworks are the archetype.
  • Federated GraphQL where every schema change is expensive due to cross-team coordination (patterns/federated-graphql-subgraph-per-domain).
  • Client-rendered payloads where the payload is interpreted by a renderer (not joined with other GraphQL fields at query time).

When not to use it

  • Fields that need to be queried, filtered, or aggregated on the server side — keep those typed.
  • APIs consumed by third parties who expect schema-driven type generation.
  • Content with security implications (access control, PII) where schema-level field-visibility is load-bearing.

Alternatives

  • GraphQL unions per element type — fully typed, expensive to evolve, best when element count is small and stable.
  • Schema-per-element with @defer / @skip — more modern GraphQL can use @oneOf or federated interfaces, but each new element still requires schema work.
  • Protocol Buffers with google.protobuf.Any — the equivalent pattern outside GraphQL; same tradeoffs.

Seen in

Last updated · 476 distilled / 1,218 read