js-schema
js-schema copied to clipboard
reference another schema
Is there a way to reference another schema? Doing
var foo = schema(...); var bar = schema({"foo": foo});
bar.toJSON()
gives a flattened schema: foo is incorporated into bar, rather than bar having a reference to foo. Is there a way, instead, to generate a reference to foo? The docs only cover references to self.
No, there is no way to do that if you mean proper JSON Schema references :( It would be nice to have something like that.
I don't plan to put resources into this, but pull requests are welcome.
Yes, at least I think JSON Schema references would do. I want this for generating documentation. E.g. if there is a schema Person that is used in three other schemas, I want the docs to just say Person three times, rather than rendering the definition of Person three times.
It looks like there's already some code for generating JSON Schema $ref properties, but I'm unable to follow it. Looks like the references are always randomly generated in Schema.prototype.toJSON, rather than using the "#/..." syntax. The Schema extension is doing something with $ref, but I can't tell what. Also can't tell when the Schema extension is invoked. BaseSchema walks over the list of extensions & picks a matching one somehow?
If you can give me any hints, I could make a pull request.
js-schema's referencing support was designed for references inside a single expression (e.g. the referencing self, referencing a schema object twice, etc.), but maybe it could be extended to support proper external referencing.
The relevant code is this: https://github.com/molnarg/js-schema/blob/master/lib/BaseSchema.js#L23 The session variable stores state when serializing a single expression. This state includes an array of already serialized sub-expressions, so that if they came up again, then we can assign an id to them (randomly) and reference them.
As a workaround, you could prefill this session object with an external reference like this (after executing the toJSON function, the session object is reset, so this is for one invocation only):
> var a = schema({x:Number})
> var b = schema({q:a, p:a})
> schema.Schema.session.serialized = { objects: [a.unwrap()], jsons: [{'id':'id_of_a'}], ids: [] }
> b.toJSON()
{ type: 'object',
properties:
{ q: { '$ref': 'id_of_a', required: true },
p: { '$ref': 'id_of_a', required: true } } }
When it comes to properly implementing it, I would probably add a plus argument to the schema()
function to denote the id (but this conflicts with the current way of defining a description, so it would have to be differentiated somehow). If the resulting schema is serialized in itself, its toJSON
function would return a JSON Schema containing it's id and the proper JSON Schema, but when it is serialized as part of a larger schema, its toJSON
would only return something like {"$ref": "id_of_object"}
.
I believe what I need is output like section 7.2.3 here:
http://json-schema.org/latest/json-schema-core.html
where the $ref
refers to the 'definitions' in the current scope. So, seems like I could get close by having a mechanism to pass in 'definitions' to a schema, generate ids based on their keys, and call toJSON on them first, so any later references would be serialized as a ref. Perhaps like
var a = schema({x:Number});
var b = schema({a_id: a}, "schema b", {q:a, p:a})
I have this working as per my proposal, above, with the exception that the doc string is always first, definitions second, schema last, in the args to schema(). Doc string is required if passing definitions, to disambiguate the args.
Diff here:
https://github.com/acthp/js-schema/commit/e90bdf18ba6090ee4ba1de2f60d163976b34fafe?diff=unified
I tested the generated schema in a different tool (is-my-json-valid), and it appears to work, though I'm fuzzy on json schema refs & pointers.
Let me know if you want a pull request, or changes (unit test, docs, other).
A couple issues that have come up:
-
makeReference
inBaseSchema.prototype.toJSON
prevents this from working on types other thanObject
andArray
. I'm not sure what this flag is for. Unit tests pass w/o it. Is there a case where making a reference is inappropriate? - It's up to subclasses to check for
$ref
and return, which is currently not done consistently across all the subclasses.regexp
for example does not check it, resulting in json that includes both$ref
and the subschema definition. Not sure if this mirrorsmakeReference
(no need to check$ref
if it can't be set), or if there's some other reason to not check for$ref
. I could add the check to all subclasses.