Flatten nested unions in `Schema.toJsonSchemaDocument`
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Newbie friendliness
- 74/100
- Issue type
- Feature
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- json, typescript
- Domain
- backend
Research direction
Read packages/effect/src/internal/schema/toJsonSchemaDocument.ts, especially compileJsonSchema's Union case around the linked lines 611–623, and compare it with the existing nested allOf handling in appendJsonSchema around lines 187–190. Add coverage for nested anyOf output, including members with annotations or $refs and the stated oneOf limitation. Done when eligible nested anyOf members are flattened while other members retain their structure.
Written by the indexing model from the issue text.
Description
What is the problem this feature would solve?
The current output is valid; this is about its shape. Schema.toJsonSchemaDocument writes each member of a Union as one anyOf option, whatever that member is. NullOr of a union, or a union of unions, then comes out as an anyOf inside an anyOf:
import { Schema } from 'effect';
const Person = Schema.Struct({ name: Schema.String });
const Company = Schema.Struct({ company: Schema.String });
const Team = Schema.Struct({ team: Schema.String });
const owner = Schema.NullOr(Schema.Union([Person, Company]));
const member = Schema.Union([Schema.Union([Person, Company]), Team]);
console.log(JSON.stringify(Schema.toJsonSchemaDocument(owner).schema));
console.log(JSON.stringify(Schema.toJsonSchemaDocument(member).schema));
Output (abridged):
{"anyOf":[{"anyOf":[{…name…},{…company…}]},{"type":"null"}]}
{"anyOf":[{"anyOf":[{…name…},{…company…}]},{…team…}]}
The nested form means the same as the flat one, but it's harder to read in rendered docs, and client generators turn it into nested union types.
What is the feature you are proposing to solve the problem?
Write the flat form:
{"anyOf":[{…name…},{…company…},{"type":"null"}]}
{"anyOf":[{…name…},{…company…},{…team…}]}
In the Union case of compileJsonSchema, an anyOf member whose only keyword is anyOf can be replaced by its own members. A member carrying anything else, like an annotation or a $ref to a definition, has to stay as it is:
appendJsonSchema already splices a nested allOf this way:
The oneOf mode can't be flattened like this: a value matching every option of { oneOf: [{ oneOf: [a, b] }, c] } is accepted, while { oneOf: [a, b, c] } rejects it.
What alternatives have you considered?
Flattening the output afterwards, in each tool that consumes it. That's being proposed for an OpenAPI converter too, but output used directly, and every other consumer, would stay nested. The same nesting comes from other libraries' JSON Schema output:
- standard-community/standard-openapi#41
- open-circle/valibot#1710
- colinhacks/zod#6673
Version: effect 4.0.5
- Dominant language
- TypeScript
- Stars
- 16.7k
- Forks
- 808
- Avg merge
- 10h 22m
- Merged PRs (30d)
- 771
Getting set up
This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from Effect-TS/effect
-
BrowserWorkerRunner: port finalizer throws when the worker global has no close() (Bun)Possibly taken @santiago-ramos-02 claimed this 18 days ago. Open
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Effect-TS/effect#8635 · 3 comments ·
Maintainers usually reply within 1 day
-
Support {self: this} for fnUntracedMay be free again @ArjunCodess claimed this 30 days ago, and no pull request is open. Openenhancement
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
Effect-TS/effect#8101 · 1 comment ·
Maintainers usually reply within 1 day
-
HashMap.set keeps the old value when the new one is Equal to itPossibly taken @schutzelaars claimed this today. Open
Difficulty 3/5 1-2 days Newbie friendliness 58/100
Maintainers usually reply within 1 day
-
OpenApi: optional query and header parameters are documented as nullablePossibly taken @Marve10s claimed this 1 day ago. Open
Difficulty 4/5 3-5 days Newbie friendliness 57/100
Maintainers usually reply within 1 day
-
OpenApi.fromApi drops the data schema of `HttpApiSchema.StreamSse({ data })`: `Schema.fromJsonString` no longer emits `contentSchema`Possibly taken @effect-bot claimed this 2 days ago. Open
Difficulty 4/5 3-5 days Newbie friendliness 35/100
Effect-TS/effect#9353 · 1 comment ·
Maintainers usually reply within 1 day
All issues in Effect-TS/effect
Similar issues
-
[测试] 等待仓库加载后再记录多仓库排序基线Possibly taken @myot233 claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
Maintainers usually reply within 1 day
-
area:arcade p3-medium ready-for-agent testing type:docs
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
fderuiter/deruiter.dev#2369 ·
Maintainers usually reply within 1 day
-
area:devenv origin:agent type:bug
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
overengineeringstudio/effect-utils#1949 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
ccch1mneyyy/dsh-TUI#1631 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
MiniMax-AI/minimax-code#507 ·
Maintainers usually reply within 1 day