-
Notifications
You must be signed in to change notification settings - Fork 7
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
Using @annotation
on a @type
#24
Comments
I think the I don't quite understand your thoughts on |
I do agree that that syntax reasonably won't fly. I'm not sure that using I'll reply in sections here, to explain how I've reasoned for context, along with a better workaround and a possible improvement for JSON-LD-Star. First Workaround ApproachMy thought regarding {
"@context": {
"@vocab": "http://example.org/ns#",
"type": {"@id": "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", "@type": "@vocab"}
},
"type": "Item"
} But that ends up with the same problem in that we cannot use {
"@context": {
"@vocab": "http://example.org/ns#",
"type": {
"@id": "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
"@context": {"@base": "http://example.org/ns#"}
}
},
"type": {
"@id": "#Item",
"@annotation": {
"assertedBy": {"name": "Somebody"}
}
}
} due to: resolve_iri("http://example.org/ns#", "Item") == "http://example.org/Item" # Undesired
resolve_iri("http://example.org/ns#", "#Item") == "http://example.org/ns#Item" # What We Want Requiring users of Further TroubleAlas, I thought, this thus further extends to any term defined using {
"@context": {
"@vocab": "http://example.org/ns#",
"language": {
"@type": "@vocab",
"@context": {"@vocab": "http://example.net/language#"}
}
},
"language": "eng"
} Would encounter the very same problem if the value of (Of course, the workaround would work better with A Better WayAt this point, I realized that I could possibly use Thus, the example with language becomes: {
"@context": {
"@vocab": "http://example.org/ns#",
"language": {
"@type": "@vocab",
"@context": {"@vocab": "http://example.net/language#"}
}
},
"language": {
"@set": "eng",
"@annotation": {
"assertedBy": {"name": "Somebody"}
}
}
} Which properly expands to: _:b0 <http://example.org/ns#language> <http://example.net/language#eng> {| :assertedBy [ :name "Somebody" ] |} . And finally, this makes the workaround for {
"@context": {
"@vocab": "http://example.org/ns#",
"type": {"@id": "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", "@type": "@vocab"}
},
"type": {
"@set": "Item",
"@annotation": {
"assertedBy": {"name": "Somebody"}
}
}
} A Slight Improvement For JSON-LD-StarAt this stage, it might be argued that {
"@context": {
"@vocab": "http://example.org/ns#"
},
"@type": {
"@set": "Item",
"@annotation": {
"assertedBy": {"name": "Somebody"}
}
}
} Had it not already worked for expansion as per the Regardless of that proposal though, I agree it is a good idea to include this workaround in the documentation to make it clear how to use (Of course, one thing preventing this from being the practise would be if this use of |
This feels akin to an issue I just faced in m-ld (or really, in json-rql), where I wanted to transform some data in a way that would have moved a property's name from a property position (where it's const existingData = await state.read<Construct>({
// Give me all all the triples...
"@construct": { "@id": "?id", "?property": "?value" },
// ...where...
"@where": {
// ...the triple is in the graph, and...
"@graph": {
"@id": "?id",
"?property": "?value",
},
// ...the variables `?id` and `?property` are bound to any of the
// id-property pairs in the data we're trying to insert--that is, it's a
// subject-property pair where we're trying to write a new value, and will
// need to delete the old one.
"@values": subjects.flatMap((subject) =>
// For every property in the data we're writing for this subject...
Object.keys(subject)
// ...except the `@id`...
.filter((key) => key != "@id")
// ...give us a `@values` binding of the subject's `@id` and that
// property.
.map((key) => ({
"?id": { "@id": subject["@id"] },
"?property": { "@id": key },
})),
),
},
}); The problem with this code is the way "?property": { "@id": key } If the properties given are all absolute IRIs, that works; but if they're relative, this will resolve them relative to @gsvarovsky's current solution was for m-ld to support the use of "?property": { "@vocab": key } Since {
"@context": {
"@vocab": "http://example.org/ns#",
"type": {"@id": "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", "@type": "@vocab"}
},
"type": {
"@vocab": "Item",
"@annotation": {
"assertedBy": {"name": "Somebody"}
}
}
} It does, unfortunately, require extending JSON-LD itself, and may be a bigger conversation, but A counter-argument in this case, though: the term is already defined as |
This creative use of The use of |
I think the overloading of keywords in different situations has created issues in the past (e.g, As for |
A I do fear that it would be quite the overloading though, and more importantly, not easy to read (it might seem to set the But, as @gkellogg says, this in itself is not an issue for json-ld-star. @Peeja would you mind raising your issue over at https://github.com/w3c/json-ld-syntax, so we can reference that here (and discuss these details over there)? |
As for the issue at hand, there is one form for which a "symbol"-key wouldn't be a solution, but which this I didn't know about the prohibition of If (That would be similar in design to regular RDF qualification but without the need for distinct qualified properties (which RDF* is for, for better or worse), and even more close to https://schema.org/Role (but with one special key instead of that design's odd repetition of the predicate). It does fully preclude what I assume is a design goal though; that annotations should in general be ignorable by "casual consumers".) |
I've been mulling some more over this case, and I'd like to share my thoughts so far, along with a new concrete proposal. MotivationsTo keep compact JSON-LD predictable and succinct enough even for annotations. Of special concern are values for Example DataThis example (given in Turtle-star) represents a diff result from two revisions of a description, in a kind of "blame" mode: prefix : <https://schema.org/>
prefix diff: <http://example.org/graph-diff#>
</item/1> a :CreativeWork {| diff:addedIn <rev1> ; diff:removedIn <rev2> |} ,
:Book {| diff:addedIn <rev2> |} ;
:datePublished "2021-12-22"^^:Date {| diff:addedIn <rev2> |} . (This is a simplified form of a concrete graph diff/blame case we have in the national union catalogue at the National Library of Sweden.) First Attempt: Brittle PairingsAll of these suffer from a value and annotation coordination problem, which I'd say is brittle. But I present them here to put all options on the table. A. One way would be to intertwine strings and "pure" annotation objects, by pairing them up ((string, annotation), ...), like: {
"@context": {
"@vocab": "https://schema.org/",
"datePublished": {"@type": "Date"},
"diff": "http://example.org/diff#",
"addedIn": "diff:addedIn",
"removedIn": "diff:removedIn"
},
"@id": "/item/1",
"@type": [
"CreativeWork",
{"@annotation": { "addedIn": {"@id": "rev1"}, "removedIn": {"@id": "rev2"}}},
"Book",
{"@annotation": { "addedIn": {"@id": "rev2"} }}
],
"datePublished": [
"2021-12-22",
{"@annotation": { "addedIn": {"@id": "rev2"} }}
]
} That may not be too bad, but it isn't a predictbly strict form of JSON (mixed strings and objects, making for some tricky coding, and which e.g. Elasticsearch would have a hard time handling). Also, it would force annotated single values to be a pair array. B. Define a "magic annotation key" pattern, where regular JSON keys have annotation key counterparts, constructed by appending {
// "@context": ...,
"@id": "/item/1",
"@type": ["CreativeWork", "Book"],
"@type @annotation": [
{ "addedIn": {"@id": "rev1"}, "removedIn": {"@id": "rev2"}
},
{ "addedIn": {"@id": "rev2"} }
],
"datePublished": "2021-12-22",
"datePublished @annotation": { "addedIn": {"@id": "rev2"} },
} This has no drawbacks for predicability of the JSON, as it always adds, never alters, unannotated compact JSON-LD. It is however, quite unacceptable as the "magic key" form is weird, not nice for algorithms, and certainly looks and feels like a hack. C. A structural variant of the "magic annotation key", albeit with more coordination problems, would be to provide a separate "annotation index": {
// "@context": ...,
"@id": "/item/1",
"@type": ["Thing", "Book"],
"datePublished": "2021-12-22",
"@annotation": {
"@index": {
"@type": [
{ "addedIn": {"@id": "rev1"}, "removedIn": {"@id": "rev2"}
},
{ "addedIn": {"@id": "rev2"} }
],
"datePublished": { "addedIn": {"@id": "rev2"} }
}
}
} That is so out of band though that it doesn't seem ergonomic neither to edit nor to consume. My personal conclusion is that neither of these options will do, not the least ergonomically (for a "JSON hacker", e.g. during regular web development, using these forms directly). I would argue that due to the nature of JSON and the compact forms sought after, there's no "room" for annotations syntactically. But we don't have to give up just yet. Since JSON-LD is able to cater for e.g. language tags (through indexed language containers) and other more advanced forms of RDF (including indexed graph containers!) in a compact and succinct way, let's explore that option too. A Better Form: Annotation Containers{
"@context": {
"@vocab": "https://schema.org/",
"diff": "http://example.org/diff#",
"addedIn": "diff:addedIn",
"removedIn": "diff:removedIn",
"typeAnnotated": {
"@container": "@annotation",
"@id": "@type",
"@value": "type"
},
"datePublishedAnnotated": {
"@container": "@annotation",
"@id": "datePublished",
"@value": "value",
"@type": "Date"
}
},
"@id": "/item/1",
"typeAnnotated": [
{
"type": "CreativeWork",
"addedIn": {"@id": "rev1"},
"removedIn": {"@id": "rev2"}
},
{
"type": "Book",
"addedIn": {"@id": "rev2"}
}
],
"datePublishedAnnotated": {
"value": "2021-12-22",
"addedIn": {"@id": "rev2"}
}
} In the form above, a new kind of term definition is introduced, called "annotation containers". It is defined by including a In the data, the values for a term defined as an annotation container are the actual annotations, except for the special key defined for the term using This feature would also allow forms like this: {
"@context": {
"@vocab": "https://schema.org/",
"contribution": {
"@id": "contributor",
"@container": ["@annotation", "@set"],
"@value": "agent"
}
},
"@id": "/item/1",
"contribution": [
{
"roleName": "Author of the introduction",
"agent": {"@id": "/agent/one"}
}
]
} Expanding to: prefix : <https://schema.org/>
</item/1> :contributor </agent/one> {| :roleName "Author of the introduction" |} . In order for this proposal to work, we would have to accept two things in its design:
It must also be algorithmically vetted, of course. Thoughts? |
@niklasl thanks for the detailed analysis. It is reminiscent of the schema.org Role pattern (although without explicit intermediaries), where an intermediate object is used to hold properties on the relationship; something which was also considered for RDF-star. In this case, however, it's an entirely syntactic construct, so doesn't have an impact on the actual data model. One thing that bothers me, though, is that the {
"@context": {
"@vocab": "https://schema.org/",
"diff": "http://example.org/diff#",
"addedIn": "diff:addedIn",
"removedIn": "diff:removedIn",
"typeAnnotated": {
"@container": "@annotation",
"@id": "@type"
},
"datePublishedAnnotated": {
"@container": "@annotation",
"@id": "datePublished",
"@type": "Date"
}
},
"@id": "/item/1",
"typeAnnotated": [{
"@type": "CreativeWork",
"addedIn": {"@id": "rev1"},
"removedIn": {"@id": "rev2"}
}, {
"@type": "Book",
"addedIn": {"@id": "rev2"}
}],
"datePublishedAnnotated": {
"datePublished": "2021-12-22",
"addedIn": {"@id": "rev2"}
}
} If the syntactic representation of the form of the Do you suggest this as an addition to the existing annotation mechanism, or as a replacement? |
Thanks @gkellogg for your further consideration. Your alternative is very interesting, and may be useful for many cases (it does exactly mirror the </item/1>
a :Nothing { a :Fallacy ; source <#issuecomment-x> } ;
:date "1900" { :date "2021"; source <#issuecomment-x> } . I wouldn't necessarily rule this alternative out just yet, but it needs to be carefully considered whether this shortcoming is more than an edge case, since it might crop up as a real issue, perhaps later on in the wild, when it would be hard, if at all possible, to address it. Thus it seems valuable to be able to control what the "special key for the object" is to be. We know that I did suggest this as an addition, for use with compact forms. The existing proposed form for annotations in JSON-LD does work for the expanded form, and for node references in compact form (unless an With that said, I will present a replacement form at the end of this comment. Anyone reading this can feel free to jump to that "Alternative 4" below if this gets long-winded (again, I present my alternatives and reasoning here for full comprehension). Alternative 2: Bare and Scoped Object KeysGoing back to If we define a "bare" form, to be used within annotation containers to provide the object itself, we can then make use of scoped contexts to select our own, contextually comprehensible key for the object, depending on the term for which the annotation container is defined. Going down that route, scoped context on annotation containers would likely be the common pattern, to define a contextually relevant name for the annotated value itself (instead of a "bare" key). Perhaps the preferred choice would be the repeated-property-pattern reminiscent of Ruling out
Here using {
"@context": {
"@vocab": "https://schema.org/",
"diff": "http://example.org/diff#",
"addedIn": "diff:addedIn",
"removedIn": "diff:removedIn",
"typeAnnotated": {"@container": "@annotation", "@id": "@type"},
"datePublishedAnnotated": {
"@container": "@annotation",
"@id": "datePublished",
"@type": "Date"
}
},
"@id": "/item/1",
"typeAnnotated": [
{
"@set": "CreativeWork",
"addedIn": {"@id": "rev1"},
"removedIn": {"@id": "rev2"}
},
{
"@set": "Book",
"addedIn": {"@id": "rev2"}
}
],
"datePublishedAnnotated": {
"@set": "2021-12-22",
"addedIn": {"@id": "rev2"}
}
} To achieve a desired form, use a scoped context to alias the bare key accordingly (here per my first example, you could use to get the {
"@context": {
"@vocab": "https://schema.org/",
"diff": "http://example.org/diff#",
"addedIn": "diff:addedIn",
"removedIn": "diff:removedIn",
"typeAnnotated": {
"@container": "@annotation",
"@id": "@type",
"@context": {"type": "@set"}
},
"datePublishedAnnotated": {
"@container": "@annotation",
"@id": "datePublished",
"@type": "Date",
"@context": {"value": "@set"}
}
},
"@id": "/item/1",
"typeAnnotated": [
{
"type": "CreativeWork",
"addedIn": {"@id": "rev1"},
"removedIn": {"@id": "rev2"}
},
{
"type": "Book",
"addedIn": {"@id": "rev2"}
}
],
"datePublishedAnnotated": {
"value": "2021-12-22",
"addedIn": {"@id": "rev2"}
}
} Alternative 3a: Value-Indexed Annotation ContainersAn alternative would be if annotation containers where indexed by their values: {
"@context": {
"@vocab": "https://schema.org/",
"diff": "http://example.org/diff#",
"addedIn": "diff:addedIn",
"removedIn": "diff:removedIn",
"typeAnnotated": {
"@container": "@annotation",
"@id": "@type"
},
"datePublishedAnnotated": {
"@container": "@annotation",
"@id": "datePublished",
"@type": "Date"
}
},
"@id": "/item/1",
"typeAnnotated": {
"CreativeWork": {
"addedIn": {"@id": "rev1"},
"removedIn": {"@id": "rev2"}
},
"Book": {
"addedIn": {"@id": "rev2"}
}
},
"datePublishedAnnotated": {
"2021-12-22": {
"addedIn": {"@id": "rev2"}
}
}
} Quite intriguingly, this annotation index form would actually syntactically enforce the "set nature" of RDF. We know that the same statement simply cannot be repeated; it would state the same fact (and RDF does not count those, since there are no multisets in RDF). This is mirrored by the fact that JSON objects cannot have their keys repeated (that just overwrites them). Alternative 3b: Turning Index Items Into PairsA variant to this {
"@context": {
"@vocab": "https://schema.org/",
"diff": "http://example.org/diff#",
"addedIn": "diff:addedIn",
"removedIn": "diff:removedIn",
"typeAnnotated": {
"@id": "@type",
"@container": "@annotation"
},
"datePublishedAnnotated": {
"@id": "datePublished",
"@container": "@annotation",
"@type": "Date"
}
},
"@id": "/item/1",
"typeAnnotated": [
{
"@index": "CreativeWork",
"addedIn": {"@id": "rev1"},
"removedIn": {"@id": "rev2"}
},
{
"@index": "Book",
"addedIn": {"@id": "rev2"}
}
],
"datePublishedAnnotated": {
"@index": "2021-12-22",
"addedIn": {"@id": "rev2"}
}
} Then, getting to the previous form above, where values are index keys, would be done by explicitly declaring: Expanded Annotated TypeAs this issue started out to be specifically about type (and not all "strings with special meaning"), there is one glaring omission which we need to consider: the case of expanded annotated types. Given that JSON-LD does not expand {
"@id": "/item/1",
"@type": [
{
"@index": "CreativeWork",
"@annotation": {
"http://example.org/diff#addedIn": {"@id": "rev1"},
"http://example.org/diff#removedIn": {"@id": "rev2"}
}
}
]
} However, now it does seem that we have ended up in a somewhat complicated situation, with varying designs for expanded and compacted forms. There is another form (alluded to before) which now presents itself. Alternative 4: Annotation ObjectsThis is a replacement for the currently defined The key concept here is the introduction of annotation objects. These are objects representing the annotation of the arc itself, having one special key, (They are structurally similar to the form of annotation containers, but do not require context definitions, since they represent the default form whenever annotations are present on an arc.) (It is also quite similar to the 1. "Bare" FormIncluding an annotated {
"@context": {
"@vocab": "https://schema.org/",
"diff": "http://example.org/diff#",
"addedIn": "diff:addedIn",
"removedIn": "diff:removedIn",
"datePublished": {"@id": "datePublished", "@type": "Date"}
},
"@id": "/item/1",
"@type": [
{
"@annotated": "CreativeWork",
"addedIn": {"@id": "rev1"},
"removedIn": {"@id": "rev2"}
},
{
"@annotated": "Book",
"addedIn": {"@id": "rev2"}
}
],
"datePublished": {
"@annotated": "2021-12-22",
"addedIn": {"@id": "rev2"}
},
"exampleOfWork": {
"@annotated": {"@id": "/work/1"},
"addedIn": {"@id": "rev3"}
}
} (Note that annotation objects must be allowed as values for 2. Using Scoped ContextsA mechanism would be required for these terms to be selected for compaction, over the bare form, based on whether their objects are annotation objects. Here this is defined using a new This is for my original example: {
"@context": {
"@vocab": "https://schema.org/",
"diff": "http://example.org/diff#",
"addedIn": "diff:addedIn",
"removedIn": "diff:removedIn",
"typeAnnotated": {
"@id": "@type",
"@type": "@annotated",
"@context": {"type": "@annotated"}
},
"datePublishedAnnotated": {
"@id": "datePublished",
"@type": "@annotated",
"@context": {"value": {"@id": "@annotated", "@type": "Date"}}
},
"exampleOfWorkRef": {
"@id": "exampleOfWork",
"@type": "@annotated",
"@context": {"ref": {"@id": "@annotated", "@type": "@id"}}
}
},
"@id": "/item/1",
"typeAnnotated": [
{
"type": "CreativeWork",
"addedIn": {"@id": "rev1"},
"removedIn": {"@id": "rev2"}
},
{
"type": "Book",
"addedIn": {"@id": "rev2"}
}
],
"datePublishedAnnotated": {
"value": "2021-12-22",
"addedIn": {"@id": "rev2"}
},
"exampleOfWorkRef": {
"ref": "/work/1",
"addedIn": {"@id": "rev3"}
}
} (This example is easily adapted to follow the In general it would certainly make sense to keep Here is the "qualified relation" example from earlier, where it makes sense to use a scoped term: {
"@context": {
"@vocab": "https://schema.org/",
"contribution": {
"@id": "contributor",
"@type": "@annotated",
"@context": {"agent": "@annotated"}
}
},
"@id": "/item/1",
"contribution": [
{
"roleName": "Author of the introduction",
"agent": {"@id": "/agent/one"}
}
]
} 3. Solving the Expanded Annotated
|
Clarification: I meant of course to redefine annotation objects in "Alternative 4" above (originally I named that differently, but attempted to unify things, alas missing to clarify that part). |
Having gone through these alternatives some more, it seems to me that "Alternative 3a: Value-Indexed Annotation Containers" is also worthy of close scrutiny. That alternative caters solely for the strings that are impossible to use the existing (This being so, of course, provided that the existing I base this evaluation on the same premise that use-cases for RDF-star annotations appear to have in general: that usage, i.e. data access, should go unaltered even with the presence of annotations. As we've seen above it is alas impossible to fully reach that, but this form at least only tackles the parts that cannot be identical, in JSON, in annotated form. And for published data (or data indexed in JSON-databases or search engines), it would be possible to duplicate just these parts (in a denormalization step outside of the specs of course) to make it fully queryable as if unannotated. Like: {
"@context": {
"@vocab": "https://schema.org/",
"diff": "http://example.org/diff#",
"addedIn": "diff:addedIn",
"removedIn": "diff:removedIn",
"typeAnnotated": {
"@container": "@annotation",
"@id": "@type"
},
"datePublished": {"@type": "Date"},
"datePublishedAnnotated": {
"@container": "@annotation",
"@id": "datePublished",
"@type": "Date"
}
},
"@id": "/item/1",
"@type": ["CreativeWork", "Book"],
"typeAnnotated": {
"CreativeWork": {
"addedIn": {"@id": "rev1"},
"removedIn": {"@id": "rev2"}
},
"Book": {
"addedIn": {"@id": "rev2"}
}
},
"datePublished": "2021-12-22",
"datePublishedAnnotated": {
"2021-12-22": {
"addedIn": {"@id": "rev2"}
}
}
} Due to the compact form of the annotation index (where the values are keys), even this denormalized form is fairly readable should one need to (and again, this is a form intended for indexing and possibly "casual consumption"; not a canonical form for storage). Also, it has the added benefit of "securing" from confusion that may otherwise arise from multiple occurrences of the same object with different annotations, which is not logically diferent (and should, I presume, be merged during flattening). |
Scoped contexts might address this, if a property-scoped context were invoked on the annotation property, that same term could be interpreted differently within the annotation. Of course, other forms of the term IRI could also be used. Alternative 3aValue-Indexed Annotation Containers seems fairly intuitive and doesn't unnecessarily overload other JSON-LD concepts. But, we'd need to consider what But, good points on the complexity of the expanded form. Alternative 4Note that we've already introduced a definition of annotation objects, although your definition seems to depend on the object containing Summary
I agree, I think it comes down to these two different directions. At this stage, we shouldn't be overly biased towards one or the other, IMHO. Probably 3a is the shortest delta from our current direction. My probably not properly consider summary:
I'm hopping to hear @pchampin weigh in. |
Sorry for a late reaction. Loads of things to catch up after an offline holiday break! A comment on what @niklasl wrote:
In fact, the current design of {
"@context": "https://schema.org/",
"name": "Alice",
"knows": {
"name": "Bob"
}
} annotated: {
"@context": "https://schema.org/",
"name": "Alice",
"knows": {
"name": "Bob",
"@annotation": {
"#assertedBy": "#charlie"
}
}
} For this reason, I tend to prefer Alternative 3a to Alternative 4. |
I cannot find a convenient way to express this:
as compact JSON-LD-star using the
@annotation
design along with@type
. Am I missing something or is this a limitation of the current design?If it is, and without resorting to context trickery, I could imagine a new special form like this might do the trick:
But with that,
"@type": {"@type": "Person"}
would have to be supported in general, which isn't so nice. I guess a variant could be"@type": {"@value": "Person"}
. Alas, both forms might come off a bit like hacks for this purpose. Defining a new keyword, say@symbol
, as a companion to@id
but resolving against the context, might do it, but with all the drawbacks of yet another new keyword.(It's possible to work around this limitation and get a "fairly compact" form, e.g. by not using
@type
and resorting to a plainrdf:type
link using a regular non-@vocab
-resolved@id
value. (Or define a key in the context with a nested context where@base
is set to the value of@vocab
, albeit that'd resolve differently for e.g. hash-IRI:s as in the example.) But I'm looking for a way to leverage@type
as is, resolving to@vocab
, to keep the compact form as close to "regular" compact JSON-LD as possible.)The text was updated successfully, but these errors were encountered: