Repository navigation
Description
Runtime
node.js
Runtime version
24.x
Module version
18.1.0
Used with
standalone
Any other relevant information
These are gaps I found while using the Standard JSON Schema output added in #3102 and improved in #3107. Some are bugs producing incorrect/invalid output, some are missing handlers that have straightforward JSON Schema Draft 2020-12 equivalents.
What problem are you trying to solve?
Two bugs and several missing conversions in the ~standard.jsonSchema output:
Bugs:
1. alternatives.match('all') produces anyOf instead of allOf
const schema = Joi.alternatives().try(Joi.string(), Joi.number()).match('all');
const json = schema['~standard'].jsonSchema.input();
// Actual: { anyOf: [...] }
// Expected: { allOf: [...] }The code in alternatives.js only checks for match('one') -> oneOf and falls through to anyOf for everything else:
const matchMode = schema._flags.match ?? 'any';
if (matchMode === 'one') {
res.oneOf = matches;
}
else {
res.anyOf = matches; // 'all' ends up here too
}2. Ref arguments produce invalid JSON Schema
When a rule receives Joi.ref() instead of a literal value, the Ref object gets assigned directly as the JSON Schema keyword value:
const schema = Joi.number().min(Joi.ref('other'));
const json = schema['~standard'].jsonSchema.input();
// Produces: { type: 'number', minimum: { ref: ... } }
// This is not valid JSON SchemaNumber, string, and array rule handlers don't check whether the arg is a Ref before assigning it. Date rules partially handle this by checking date instanceof Date, but other types don't guard at all.
Since refs resolve against runtime values (not schema definitions), there's no standard JSON Schema equivalent. Draft 2020-12 has no $data keyword or cross-field reference mechanism. The fix would be to skip emitting the constraint when the arg is a Ref, similar to how the date rules already skip when the arg isn't a Date instance.
Missing conversions:
-
_invalidsnot handled:Joi.string().invalid('foo', 'bar')produces no constraint. Could benot: { enum: ['foo', 'bar'] }. -
presence: 'forbidden'not handled:Joi.object({ secret: Joi.forbidden() })includessecretinpropertiesas a normal optional field. -
result: 'strip'not handled in output mode: stripped fields still appear in the output JSON Schema despite being removed from the actual output. -
Object dependency rules (
with/without/and/etc.) not emitted.with/withoutmap cleanly todependentRequiredin Draft 2020-12:
Joi.object({ a: Joi.string(), b: Joi.string() }).with('a', 'b')
// Could produce: { ..., dependentRequired: { a: ['b'] } }-
string.alphanummissing handler. Could bepattern: '^[a-zA-Z0-9]+$'. -
number.precisionmissing handler. Could approximate withmultipleOf(e.g., precision 2 ->multipleOf: 0.01).
Question: additional target support?
The Standard JSON Schema spec strongly recommends supporting both draft-2020-12 and draft-07, and lists openapi-3.0 as a best-effort target:
type Target = "draft-2020-12" | "draft-07" | "openapi-3.0" | ({} & string);Currently Joi only supports draft-2020-12 and throws on everything else. Is there interest in adding draft-07 support? The main differences would be prefixItems -> items/additionalItems, and $defs -> definitions. openapi-3.0 would be a bigger lift since it's based on draft-04 with extensions.
Do you have a new or modified API suggestion to solve the problem?
Happy to work on PRs for any of these. The bugs (1, and 2) seem like the highest priority since they produce incorrect or invalid output.
Activity
Marsup commentedon Apr 8, 2026
Those points seem very valid indeed, I'll of course accept PRs on it.
Regarding the support of older specs, I haven't seen any elaborated justification for doing so. They say
Different tooling requires different versions of JSON Schema., sure, but which tools are they referring to? This information is nowhere to be found. I'm not in favor of adding support for this old spec unless I can understand the motivation for it.AbdelrahmanHafez commentedon Apr 11, 2026
Fair enough, I'm working on a PR for these issues (and a few other similar improvements), and will look into the different versions of JSON schema later.
Should submit a PR the next week or two.
test(json-schema): add tests for bugs, missing conversions, dependenc…
fix(json-schema): fix bugs, add missing conversions, dependencies, an…
test(json-schema): add regression coverage for schema parity re hapij…
fix(json-schema): improve conversion parity re hapijs#3108
test(json-schema): add coverage for full Unicode domain support and s…
fix(json-schema): support full Unicode (astral planes), fix stripUnkn…
test(json-schema): add coverage for meta edge cases, dependency mergi…
fix(json-schema): add coverage exclusions for unreachable defensive c…
test(json-schema): cover composite and unicode domain edge cases re h…
fix(json-schema): remove coverage ignores and tighten domain parity re …
test(json-schema): cover forbidden presence and null invalid parity re …
fix(json-schema): preserve forbidden presence and null invalid parity…
8 remaining items
fix(json-schema): fix bugs, add missing conversions, dependencies, an…
test(json-schema): add regression coverage for schema parity re hapij…
fix(json-schema): improve conversion parity re hapijs#3108
test(json-schema): add coverage for full Unicode domain support and s…
fix(json-schema): support full Unicode (astral planes), fix stripUnkn…
test(json-schema): add coverage for meta edge cases, dependency mergi…
fix(json-schema): add coverage exclusions for unreachable defensive c…
test(json-schema): cover composite and unicode domain edge cases re h…
fix(json-schema): remove coverage ignores and tighten domain parity re …
test(json-schema): cover forbidden presence and null invalid parity re …
fix(json-schema): preserve forbidden presence and null invalid parity…
test(json-schema): cover custom extension base types re hapijs#3108
fix(json-schema): preserve base types for custom extensions re hapijs…
test(json-schema): cover ordered tuple length parity re hapijs#3108
test(json-schema): cover invalid null schema branches re hapijs#3108