-
-
Notifications
You must be signed in to change notification settings - Fork 297
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
JSON Reference - An alternative model to $ref
/$id
#724
Comments
I'll be posting a walk-through of how I derived this model within a few days. If the above isn't clear, you might want to wait until my next post before asking questions. NOTE: This is not a proposal, it's a description of what I'm doing. Any changes to JSON Schema's model should be proposed as separate issues. This issue is just for clarifying this model. |
Thanks, @jdesrosiers . Whether it becomes a proposal or not it is great to have this for a reference for discussion. All: Let's limit discussion here to clarifying questions on this idea, and I recommend waiting on that until @jdesrosiers has posted his walkthrough. If this starts to turn into a debate, I'll lock this (but since I'm the person mostly likely to mess that up... um... 🤣 ) |
Deriving JSON ReferenceJSONWe start with JSON.
{
"foo": "bar"
} Now we need a way to retrieve that JSON. We'll use URIs as identifiers. const get = (uri) => {
const json = Data.fetch(uri);
const doc = JSON.parse(json);
return doc;
};
get("http://example.com/example1"); // => {"foo":"bar"} We will assume that Document ValueIn HTML, a URI fragment changes the view point for the document. It doesn't identify the document in any way. It only changes the way the browser presents the document. For JSON Reference, we want to use a similar concept of changing the view point of the document. When working with data, it makes sense to consider a fragment to be some portion of the data. I call this the "value" of the document. Let's add support for a JSON Reference fragment. The value of the document will be the JSON Pointer fragment applied to the document. const get = (relativeUri, contextDoc = {}) => {
const uri = resolveUri(resolveUri, contextDoc.uri);
const json = Data.fetch(uri);
const doc = {
uri: uri,
pointer: fragmentOf(uri),
jref: JSON.parse(json)
};
return doc;
};
const value = (doc) => JsonPointer.get(doc.pointer, doc.jref);
const example1 = get("http://example.com/example1");
const foo = get("#/foo", example1);
value(foo) // => "bar" We've modified our document representation to envelope the data so we can also store metadata like the JSON Pointer from the URI fragment. Then we added a $refNext we'll add support for references.
{
"foo": { "$ref": "#/bar" },
"bar": "bar"
} const get = (relativeUri, contextDoc = {}) => {
const uri = resolveUri(relativeUri, contextDoc.uri);
const json = Data.fetch(uri);
const doc = {
uri: uri,
pointer: fragmentOf(uri),
jref: JSON.parse(json)
};
const docValue = value(doc);
return "$ref" in docValue ? get(docValue["$ref"], uri) : doc;
};
const example2 = get("http://example.com/example2");
const foo = get("#/foo", example2);
value(foo) // => "bar" We've modified our const entries = (doc) => {
const docValue = value(doc);
Object.keys(docValue).map((key) => {
const uri = "#" + JsonPointer.append(key, doc.pointer);
return [key, get(uri, doc.uri)];
});
}; $idI wasn't going to support
{
"foo": {
"$id": "/example1",
"foo": "bar"
},
} const get = (relativeUri, contextDoc = {}) => {
const uri = resolveUri(relativeUri, contextDoc.uri);
const json = Data.fetch(uri);
const jref = JSON.parse(json, (key, value) => {
if ("$id" in value) {
const id = value["$id"];
delete value["$id"];
Data.add(id, value);
return { "$ref": id };
} else {
return value;
}
});
const doc = {
uri: uri,
pointer: fragmentOf(uri),
jref: jref
};
const docValue = value(doc);
return "$ref" in docValue ? get(docValue["$ref"], uri) : doc;
};
const foo = get("#/foo", "http://example.com/example3");
value(foo) // => {"foo":"bar"}
get("#/foo/foo", "http://example.com/example3"); // => Error: No such value If you aren't familiar with the With just these few lines of code, we now have full support for $anchorLocation-independent identifiers is another JSON Schema feature I wasn't going to support, but changed my mind. Unlike the JSON Schema version of A plain name can be used in the URI fragment instead of a JSON Pointer. If the fragment is a plain name, it refers to a location within the document with an
{
"foo": {
"$anchor": "aaa",
"bar": "bar"
}
} const get = (relativeUri, contextDoc = {}) => {
const uri = resolveUri(relativeUri, contextDoc.uri);
const json = Data.fetch(uri);
const anchors = {};
const jref = JSON.parse(json, (pointer, value) => {
if ("$id" in value) {
const id = value["$id"];
delete value["$id"];
Data.add(id, value);
return { "$ref": id };
} else if ("$anchor" in value) {
const anchor = value["$anchor"];
anchors[anchor] = pointer;
delete value["$anchor"];
return value;
} else {
return value;
}
});
const fragment = fragmentOf(uri);
const doc = {
uri: uri,
anchors: anchors,
pointer: isPointer(fragment) ? fragment : anchors[fragment],
jref: jref
};
const docValue = value(doc);
return "$ref" in docValue ? get(docValue["$ref"], uri) : doc;
};
const aaa = get("#aaa", "http://example.com/example4");
value(aaa) // => {"bar":"bar"} Now we add to our Note: The arguments of the |
I hope this illustrates how simple and consistent this model is by how easy it is to implement. I hope you find it useful. I would appreciate any feedback or questions anyone has. @handrews @awwright @Relequestual @johandorland @KayEss @epoberezkin |
@jdesrosiers while I agree that this systems is consistent within itself and supports an elegant implementation, it is not compatible with how URIs actually work. You assert:
The first sentence is true in the sense that, when a browser navigates to a URI (due to clicking on an Per RFC 3986 (emphasis mine):
URIs are used in two ways: identification and navigation. Identification involves associating a URI with all or (when using fragments) part of a resource. Directly giving the URI is one way to perform such identification, which JSON Schema currently does with You have conflated these things. You notably omitted from this explanation the case of In examples elsewhere, though, you used: {
"type": "object",
"properties": {
"aaa": {
"$id": "/common#/definitions/twentieth-century-year",
"definitions": {
"year": { "type": "string", "pattern": "^\\d{4}$" },
"twentieth-century-year": { "$ref": "#/definitions/year", "pattern": "^19" }
}
}
}
} equivalent to these two documents together: {
"type": "object",
"properties": {
"aaa": {
"$ref": "/common#/definitions/twentieth-century-year"
}
}
} {
"$id": "/common",
"definitions": {
"year": { "type": "string", "pattern": "^\\d{4}$" },
"twentieth-century-year": { "$ref": "#/definitions/year", "pattern": "^19" }
}
} I believe this works as you stated before with the code you have given here.
In order to make your system work, you have changed That is just not how URIs work. It's either identification or navigation. It's not both. |
This model uses URIs exactly the same way HTML does. If this is incompatible with URI then so is HTML. When I read what you quoted from the specification, I definitely see how my choice of words was misleading. I'll try to come up with a better way to explain it. My point is that the response you get from a server will always be the same regardless of the fragment. When I retrieve an HTML document with a URI that includes a fragment, I get the entire HTML document, not just the bit identified by the fragment. Correct me if I'm wrong, but I don't think that's disputed.
I have certainly have changed the semantics of I'm not sure I agree with or even fully understand your distinction between identification and navigation, but hopefully this clarification makes it unnecessary to go down that road.
I actually left that out on purpose as a way to gauge whether people where understanding what I am presenting. If they understood the implications of the change, this question should come up. However, there is nothing problematic about this case. It is completely consistent with the model and existing web standards. Hopefully the above clarification of |
No, you have not made
You're not wrong, but that is navigation (resolving the URL and pointing the client at the resource it identifies, and then applying the fragment within the client as opposed to sending the fragment to the server). That is not what is happening with Look at the difference between I don't see anywhere in HTML that behaves the way you assert. Also, you can't just wave away the concept of base URI. Your I'm going to stop here and see if anyone else wants to advocate for this proposal. I am against it, but let's see what others say. |
@jdesrosiers I guess if you define (yeah, I know I said I would stop commenting, but I'm really trying to understand still) |
I don't know how we can have a meaningful conversation when you refuse to accept that the things that I define mean what I say they mean. I know you're trying, but I don't see where to go from here. I'll try to describe it in a different way, but until we find some common ground, I'm sure it won't make any difference.
Maybe an example will help.
{
"aaa": {
"$id": "/bar#/bbb",
"bbb": { "$ref": "#/ccc" },
"ccc": "ddd"
}
} const aaa = get("http://example.com/foo#/aaa");
// => http://example.com/bar/#ccc
// => {
// => "bbb": { "$ref": "#/ccc" },
// => "ccc": "ddd"
// => }
value(aaa);
// => "ddd" Notice that fetching This is the primary benefit of considering |
I've been trying to work out what the differences with the current behaviour are. It seems to me that they are:
There might be other more subtle differences, but those two stand out. |
@jdesrosiers OK so you are changing the base URI. The initial base is I mean, maybe you don't call it a base URI but that's what it looks like to me. Regardless, I find this very confusing- the behavior of I understand embedding a document, and But I don't think it makes the conceptual model in JSON Schema easier. The |
Perhaps the test of whether this is truly lightening the cognitive load is to attempt to summarize -- with words, not code -- what is going on under this proposal? The simplicity of the proposal can then be judged by the merits of its terseness and clarity. One question I have is how such a proposal, which appears to be centered upon the notion of the implicit inlining of resources, would work in the context of error reporting? Validators producing standardized errors will need to be able to know "which" schema they're currently in, so the notion of following a |
Of course there's a base URI. I'm not claiming there isn't. What I'm saying is that there is always only one base URI per document. If it's necessary to change the base URI, you have to navigate to a different document. I know I'm repeating myself, but I don't know how else to say it.
Yes! This is the other thing I was hoping people would realize. The only way I found to salvage
The intermediate processing step is optional. You could handle
I think that's correct. I haven't put much thought into the full implications, but at the least, it complicates things. I'm glad you called that out.
There are two major ones. @handrews identified them almost immediately. The first is the meaning of fragments in
I thought that's what I did in the description of this issue? Clearly this isn't as straightforward as I thought. I'm biased of course, but I think if you took someone who didn't already understand the nuances of the way
I'm not sure I understand the question. How would you not know which schema you are currently in? Nothing about how |
@jdesrosiers while this is great work and makes sense for your standalone project, and I have shamelessly lifted elements into separate issues, I don't see support building for the more drastic changes to the nature of Unless a surge of people advocating for this show up in the next week, I think it's best that we close this. |
I know you're joking, but there's nothing shameless about it! This is exactly how I expected this to go down (maybe better than expected). And not worry, I'll wait until draft-08 is out before rocking the boat again 😉 |
I've been working on a generic browser concept based on JSON Reference. It's still very early stages, but I think the model I came up with is a candidate for a solution to the issues JSON Schema has with
$id
. I've been meaning to share this for a while, but have been hesitating due to uncertainty about how to present it. I've finally decided that getting something out there is better than nothing, so I'm presenting this brief overview and I'll let any questions drive any discussion.I've found this model easy and efficient to implement. It has strong parallels to existing web constructs. It simplifies the concepts without loosing anything of value.
One of the goals of this model is to fully decouple JSON Pointer, JSON Reference, and JSON Schema. Each can be implemented independently of one another. I wrote a JSON Schema-ish validation proof of concept that builds on JSON Reference (rather than JSON). This implementation has full support for
$ref
/$id
without dedicating a single line of code to supporting it.JSON Reference for JSON Schema Implementors
The features of JSON Reference are very similar to the features of
$ref
and$id
in JSON Schema. However, the concepts are slightly different and thekeywords are slightly more constrained (in a good way) than their JSON Schema
counterparts.
Documents vs Values
All JSON Reference documents have a "value". The fragment part of the document's
URI identifies the portion of the document that is considered the "value" of the
document.
If the fragment is empty, the value of the document is the whole document.
If the fragment starts with a
/
the fragment is interpreted as a JSON Pointerand the value of the document is the document resolved against the JSON Pointer.
If the fragment is not a JSON Pointer, then it's an anchor fragment. The
$anchor
keyword provides a label that marks a portion of the document. Givenan anchor fragment, the value of the document is the portion of the document
identified by an
$anchor
keyword that matches the anchor fragment.The value of a document whose URI fragment does not point to a valid part of the
document is undefined. Implementations must not cross document boundaries in
attempt to resolve a fragment.
$ref
indicates an embedded document$ref
indicates a document to be embedded. It's analogous to an<iframe/>
inan HTML document. Even
$ref
s that point to the current document are embeddeddocuments. Notice that the entire document is embedded, not just the value of
the document. However, a user agent that encounters an embedded document should
use the value of the document. It's necessary to embed the entire document in
order to properly handle any
$ref
within the embedded document.$id
is an embedded$ref
An
$id
indicates an inlined$ref
. This is similar to using the HTTP/2 pushfeature to send the document identified by the
src
attribute of an<iframe>
.It's just a network optimization for a
$ref
. This means that unlike JSONSchema, an
$id
can have a fragment and that fragment is meaningful.$anchor
is not an embedded document$anchor
provides a way to have a path-independent way to identify adocument's value without creating a document boundary.
The text was updated successfully, but these errors were encountered: