-
-
Notifications
You must be signed in to change notification settings - Fork 273
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
Proposal: Remove initial resolution step for dynamic references #1140
Comments
Here's an example that sets a dynamic anchor with the identifier {
"$id": "https://json-schema.hyperjump.io/schema/list",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"list": {
"type": "array",
"items": { "$dynamicRef": "/anchor/llist" }
},
"nextPage": { "type": "integer" },
"previousPage": { "type": "integer" },
"perPage": { "type": "integer" },
"page": { "type": "integer" }
}
} {
"$id": "https://json-schema.hyperjump.io/schema/foo-list",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$ref": "/schema/list",
"$defs": {
"foo": {
"$dynamicAnchor": "/anchor/list",
"$ref": "/schema/foo"
}
}
} |
I couldn't help writing up what the spec might look like without bookending or initial resolution (#1142). It makes the concept much easier to describe. Personally, I think it's easier to understand as well. |
Here is an example using the hyper-schema links schema that shows what is lost by removing the initial resolution step. In this case we want "submissionSchema" to be extendible with a dynamic reference, but we also want it to default to a specific draft. With initial resolution, we can do this. {
"$id": "https://json-schema.org/draft/2020-12/links",
"properties": {
"submissionSchema": { "$dynamicRef": "/draft/2020-12/hyper-schema#meta" }
}
} The dynamic reference first resolves to the 2020-12 dialect where a "meta" dynamic anchor enters the dynamic scope. Since there are no other "meta" dynamic anchors already in this scope, the 2020-12 dialect is used. Without the initial resolution, we can get the same behavior, but it's more verbose. {
"$id": "https://json-schema.org/draft/2020-12/links",
"properties": {
"submissionSchema": { "$dynamicRef": "meta" }
},
"$defs": {
"defaultDialect": {
"$dynamicAnchor": "meta",
"$ref": "/draft/2020-12/hyper-schema"
}
}
} In order for there to be a default place for the dynamic reference to resolve, we have to explicitly set a dynamic anchor somewhere in the schema to define the default. |
I just read back through this and the previous issue, as I never responded to your last example there which you have expanded on here. I agree that this works! While it's true that this particular case becomes more verbose, I think that's actually a good thing. The mutual recursion case is very hard to wrap your head around, and having explicit starting points helps make it a lot more clear what is happening. Here's my last example from the other issue, updated based on your current proposal, just to make sure I'm reading things correctly (We'll just pretend like there's a 2020-12 hyper-schema even though there isn't): {
"$id": "https://json-schema.org/draft/2020-12/links",
"$dynamicAnchor": "links-schema",
"type": "object",
"properties": {
"submissionSchema": { "$dynamicRef": "meta" }
},
"$defs": {
"defaultDialect": {
"$dynamicAnchor": "meta",
"$ref": "https://json-schema.og/2020-12/hyper-schema#meta"
}
}
} {
"$id": "https://json-schema.org/draft/2020-12/hyper-schema",
"$dynamicAnchor": "meta",
"type": "object",
"properties": {
"items": {"$dynamicRef": "meta"},
"links": {
"type": "array",
"items": {"$dynamicRef": "links-schema"}
}
},
"$defs": {
"defaultLinksSchema": {
"$dynamicAnchor": "links-schema",
"$ref": "https://json-schema.og/2020-12/links#links-schema"
}
}
} @jdesrosiers I see that your solution accomplishes several things compared to 2020-12:
I definitely find that last point appealing. TBH I'm not sure why I thought the two systems should interact. I like it! Definitely an improvement 😃 |
@handrews Thanks for looking this over. Your timing is perfect because I was hoping to get back to working on this issue soon. Your example is pretty much what I had in mind except there should be no need for the What are your thoughts about making dynamic anchors URIs? My main concern is that people will find it confusing even tho there's precedence in the web space (link relations).
|
Oh, right- at least I think I understand. There are actually two things here:
Are both of those correct? I think in some circumstances it would be fine to declare both a static
I think it will be confusing simply because you write the same dynamic anchor in multiple places, and that's not how URIs are normally used. It's fine with link relations, because you aren't targeting them in those different locations. They serve as references to or identifiers for the semantics of the link relation type. The target that they identify is the specification, and there's only one of those for each link relation URI. Making I think it's better to keep With link relation types, there is a globally shared notion of semantics. That's not really the same for |
Yep
I think we could go either way with this. We could still allow dynamic anchors to be referenced by
Yeah, you make some good points. I think it's not likely to be needed in enough cases to be worth the added complexity and there are plenty of ways to namespace a dynamic anchor that are compatible with plain-name fragments for the cases where it is needed. |
I'm in favor of making this change for the next release. We do need to figure out our compatibility story here, as the semantics of
Just changing it would be more appealing if we can definitely automatically migrate existing uses to a combination of the new syntax and Of course, all of this is relevant to / in the context of #1242 . |
I think we can just change it. We aren't committed to no backwards compatible changes yet. This is definitely something we want to get in before we consider this feature stable. |
I'm also in favor of just changing it. I think a summary is warranted, though. From what I can see
Is that correct? Am I missing anything? |
@gregsdennis yes to both of those points. Pulling in some additional details from the behaviors slides, I think this boils down to:
|
To be clear, the bookending requirement was already removed in #1139. This proposal is in addition to that change. |
Not 100% sure this proposal can replace this example: {
"$id": "https://test.json-schema.org/dynamic-ref-leaving-dynamic-scope/main",
"if": {
"$id": "first_scope",
"$defs": {
"thingy": {
"$comment": "this is first_scope#thingy",
"$dynamicAnchor": "thingy",
"type": "number"
}
}
},
"then": {
"$id": "second_scope",
"$ref": "start",
"$defs": {
"thingy": {
"$comment": "this is second_scope#thingy, the final destination of the $dynamicRef for then",
"$dynamicAnchor": "thingy",
"type": "null"
}
}
},
"else": {
"$id": "third_scope",
"$ref": "start",
"$defs": {
"thingy": {
"$comment": "this is third_scope#thingy, the final destination of the $dynamicRef for else",
"$dynamicAnchor": "thingy",
"type": "null"
}
}
},
"$defs": {
"start": {
"$comment": "this is the landing spot from $ref",
"$id": "start",
"$dynamicRef": "inner_scope#thingy"
},
"thingy": {
"$comment": "this is the first stop for the $dynamicRef",
"$id": "inner_scope",
"$dynamicAnchor": "thingy",
"type": "string"
}
}
} It might be necessary to change |
What about this example do you think won't work? The {
"$id": "https://test.json-schema.org/dynamic-ref-leaving-dynamic-scope/main",
"if": {
"$id": "first_scope",
"$defs": {
"thingy": {
"$dynamicAnchor": "thingy",
"type": "number"
}
}
},
"then": {
"$id": "second_scope",
"$ref": "start",
"$defs": {
"thingy": {
"$dynamicAnchor": "thingy",
"type": "null"
}
}
},
"$defs": {
"start": {
"$id": "start",
"$dynamicRef": "thingy"
}
}
} |
@jdesrosiers does Let's look at what I thought would happen, what I now think you are proposing, and another option that has come to mind for me. For all of these I put four locations at the beginning of each numbered step:
The last three are the bits of info from the output unit, as I'm sure you know but other readers may not. The document-relative one is for my sanity while tracking all of the For all of these, the implementation steps are not necessarily what an implementation would have to do, just how I am thinking of it - any alternate implementation that produces the same outcome would be fine. What I thought this was proposingThis is the behavior described in my presentation (original color; accessible color).
With this in mind, evaluation looks like:
What I now think you are proposingI'm thinking about this in a 2-pass system- again, that doesn't mean it must be implemented this way. Loading pass:
Given these mappings, evaluation would look like:
This works, but I find the implicit combination of lexical and dynamic behavior in the An approach that explicitly separates behaviorsThis approach has This requires a slightly different schema layout, which I think is more intuitive to read. I've used {
"$id": "https://test.json-schema.org/dynamic-ref-leaving-dynamic-scope/main",
"if": {
"$id": "first_scope",
"$dynamicAnchor": "x",
"$defs": {
"thingy": {
"$anchor": "thingy",
"type": "number"
}
}
},
"then": {
"$id": "second_scope",
"$dynamicAnchor": "x",
"$ref": "start",
"$defs": {
"thingy": {
"$anchor": "thingy",
"type": "null"
}
}
},
"$defs": {
"start": {
"$id": "start",
"$dynamicRef": ["x", "#thingy"]
}
}
} With this approach, the loading pass is normal with only
This has the same result as the previous approach, but makes the dual dynamic + static lookup explicit in the reference, rather than implicit in a mapping. Resolving I had somehow thought previously that the two steps could be separated by using combintations of |
@handrews I especially have no idea what you mean by mixing lexical and dynamic behavior. Your alternate approach just seems to complicate the concept. I can't see the benefit. I see Here's a technical walk-through similar to what you presented. I only share in hopes that it helps communicate my mental model and thus improve communication. As with your example, the implementation details aren't important, as long as the behavior is the same.
dynamicAnchors = {
".../main": {},
".../first_scope": { thingy: "/$defs/thingy" },
".../second_scope": { thingy: "/$defs/thingy" }
".../start": {}
}
|
@jdesrosiers thanks for working through my last comment despite the difficulty. I'm sorry it was not more clear or helpful.
I believe what you're saying is identical to what I laid out in the "What I now think you are proposing", except for implementation details that are unimportant if not irrelevant.
That's a very good point. Arguably, since
If there is a benefit (and TBH there may not be), it's that it makes the inherent complexity of the behavior explicit rather than implicit. I'm going to explain this in the hopes that it fosters understanding of our divergent mental models, rather than as an argument that we need to adopt the To me, there are three aspects of
I personally find the combination of points 2 and 3 to be confusing and unintuitive. I originally thought that you were removing aspect 3, which dramatically simplified the behavior by making The Because what I would like is something like the following (which is not at all possible as written, and which I am not proposing. It is kind of like JSON Schema pseudo-code... psuedo-schema?). I'm going to use {
"$id": "https://test.json-schema.org/dynamic-ref-leaving-dynamic-scope/main",
"if": {
"$id": "first_scope",
"$setJump": {
"jumpAnchor": "x",
"onJump": {"$ref": "#thingy"}
},
"$defs": {
"thingy": {
"$anchor": "thingy",
"type": "number"
}
}
},
"then": {
"$id": "second_scope",
"$setJump": {
"jumpAnchor": "x",
"onJump": {"$ref": "#thingy"}
},
"$ref": "start",
"$defs": {
"thingy": {
"$anchor": "thingy",
"type": "null"
}
}
},
"$defs": {
"start": {
"$id": "start",
"$longJump": "x"
}
}
} Here it should be clear that Again, I'm not suggesting this - it's even more verbose and requires duplicating the But it does show how I conceptualize However, if I'm the only one who finds that implicit behavior combination difficult, then there is no benefit to making it explicit. |
This issue is a spin-off of #1064. That conversation drifted a bit, so I'm moving some of the proposed amendments to that proposal into a separate issue.
Removing the bookending requirement for dynamic references opens the door for some additional considerations.
$recrusiveRef
didn't allow non-fragment URI parts.$dynamicRef
added that in order to allow using$dynamicRef
in a non-recursive schema. However, without the bookending requirement, the initial resolution step is no longer necessary (see #1064 (comment)). Since it's not necessary, there exists the possibility that a$dynamicRef
doesn't need to be a URI at all. It makes sense for it to just be the dynamic anchor name. There's no reason to encode it into a URI. (Credit to @ssilverman for this idea) (See also #1064 (comment)).We could take this a step further and make dynamic anchors URIs instead of plain name identifiers. Because dynamic anchors aren't scoped to the local document like traditional anchors, there's a possibility of name conflicts between schemas, so using URI identifiers would be safer. It's a similar concept to using URIs for link relations (for those who are familiar with those).
The text was updated successfully, but these errors were encountered: