Skip to content

Support mixed field kinds for the same path across schema branches #95

@fabian-hiller

Description

@fabian-hiller

Related to #93.

Summary

Formisch currently assumes that a given field path has one structural kind during initialization. Arrays and plain objects are treated specially because they create nested field stores, while all other values are treated as leaf fields backed by a single signal for the value.

That means the same object key or array item cannot currently be:

  • a leaf field in one branch, and
  • an object or array field in another branch

This is not limited to variant schemas. The same limitation also applies to unions and other schema compositions where multiple branches can reuse the same path with different structural kinds.

Current situation

Today, Formisch builds one shared field-store tree from the schema. This works well as long as each path resolves to a consistent kind across all relevant branches.

The limitation appears when branches reuse the same path but assign incompatible kinds, for example:

  • value: string in one branch
  • value: string[] in another branch
  • item: number in one branch
  • item: { nested: string } in another branch

In those cases, the same path would need to behave both like a leaf and like a structural node, which the current store model does not support.

For now, the recommended workaround is to use different object keys for fields that have different structural kinds across branches.

Why this exists

This is mostly a tradeoff in the original implementation. Treating arrays and plain objects as structural nodes keeps the store model simple and efficient, while every non-array and non-plain-object value can stay a leaf signal for the value.

Supporting multiple kinds for the same path is theoretically possible, but it introduces significantly more complexity in initialization, reconciliation, updates, and type handling. That complexity was not worth investigating in the initial implementation.

Possible directions

Some possible approaches:

  1. Make initialization branch-aware and only build the active branch when a schema composition can be narrowed.
  2. Keep separate branch-local stores and switch between them based on the active schema branch.
  3. Delay materializing a field store until the active branch is known.
  4. Add a reconciliation layer that can replace a leaf field with an object or array store, and vice versa, when the active branch changes.
  5. Introduce a more abstract store representation that can model unresolved paths before collapsing them into a concrete kind.

Each option has tradeoffs around performance, state preservation, implementation complexity, and framework integrations.

Scope

This is a known limitation, and it is probably something we should work on after the v1 release rather than before. The current workaround is acceptable for now, but the limitation should be tracked explicitly because it affects schema design for variants, unions, and similar multi-branch schemas.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions