Skip to content
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

Scoping context based on the @type #415

Closed
mitar opened this issue Mar 21, 2016 · 17 comments
Closed

Scoping context based on the @type #415

mitar opened this issue Mar 21, 2016 · 17 comments

Comments

@mitar
Copy link

mitar commented Mar 21, 2016

Currently, for JSON data we have, I have to put @contexts all around the JSON to scope properties:

{
    "@context": {
        "@base": "http://127.0.0.1:8000/api/v2/node/",
        "@vocab": "http://127.0.0.1:8000/api/v2/node/?format=vocab#"
    },
    "@type": "Node",
    "@id": "000c3d9d-5d7c-492a-8d3b-bb8b922ae35f",
    "config": {
        "core.location": {
            "@context": {
                "@base": "_:config/core.location/",
                "@vocab": "http://127.0.0.1:8000/api/v2/node/?format=vocab#config/core.location/"
            },
            "@id": "123",
            "@type": "Location",
            "timezone": "Europe/Ljubljana",
            "address": "San Juan, Puerto Rico",
            "city": "Ljubljana",
            "country": "SI",
            "geolocation": {
                "@context": "http://geojson.org/geojson-ld/geojson-context.jsonld",
                "type": "Point",
                "coordinates": [
                    -66.073756,
                    18.378287
                ]
            },
            "altitude": 0.0
        },
        "core.project": {
            "@context": {
                "@base": "_:config/core.project/",
                "@vocab": "http://127.0.0.1:8000/api/v2/node/?format=vocab#config/core.project/"
            },
            "@id": "444",
            "@type": "Project",
            "project": "http://127.0.0.1:8000/api/v2/project/2"
        }
    }
}

It would be great if I could somehow @vocab (or @context) itself would be scoped based on the value of @type, similarly to what I have to do currently manually. Ideally, JSON-LD would then be something like:

{
    "@context": {
        "@base": "http://127.0.0.1:8000/api/v2/node/",
        "@vocab": "http://127.0.0.1:8000/api/v2/node/?format=vocab#"
    },
    "@type": "Node",
    "@id": "000c3d9d-5d7c-492a-8d3b-bb8b922ae35f",
    "config": {
        "core.location": {
            "@id": "_:config/core.location/123",
            "@type": "config/core.location/Location",
            "timezone": "Europe/Ljubljana",
            "address": "San Juan, Puerto Rico",
            "city": "Ljubljana",
            "country": "SI",
            "geolocation": {
                "@context": "http://geojson.org/geojson-ld/geojson-context.jsonld",
                "type": "Point",
                "coordinates": [
                    -66.073756,
                    18.378287
                ]
            },
            "altitude": 0.0
        },
        "core.project": {
            "@id": "_:config/core.project/444",
            "@type": "config/core.project/Project",
            "project": "http://127.0.0.1:8000/api/v2/project/2"
        }
    }
}

And all definitions of properties would stay exactly the same as in the initial JSON at the top.

@niklasl
Copy link
Member

niklasl commented Mar 21, 2016

👍 Yes, if nested contexts are to be supported, I think connecting them to @type is a good approach. Expectation on shape/terms is commonly connected to @type.

The alternative (as I've seen it) – to tie nested contexts to specific properties (links) – would risk an increase in fragmentation (e.g. if a Person used title for foaf:title when linked to with dc:creator but not dc:contributor).

@niklasl
Copy link
Member

niklasl commented Mar 21, 2016

I should probably balance my opinion by saying that I think both alternatives have merit. A possible solution is to support term-controlled contexts in both cases (i.e. apply it both for terms with local context used as predicates, and for those used as values of @type).

(I guess the harder question is whether the local context would extend the base or not (I'd wager for extension by default).)

@dlongley
Copy link
Member

👍 to adding both @type and property scoped contexts.

There's also an interesting JSON transformation tool worth mentioning here that could be combined with some of these future JSON-LD features (potentially useful for framing as well): https://github.com/bazaarvoice/jolt

@gkellogg
Copy link
Member

In addition to #247, #262, see also #315.

@azaroth42
Copy link
Contributor

azaroth42 commented Feb 1, 2017

A use case for this that would solve a significant pain point in the cultural heritage domain is the over-reliance on domain and range specific predicates, and using type based contexts to reduce them back to a smaller and more understandable set of JSON keys.

For example, in CIDOC-CRM there are several pairs of hasPart/partOf predicates based on the domain. These could be collapsed down to part and partOf per class without ambiguity as the domains and ranges do not overlap. In English:

  • Time Periods consist of / form part of other Time Periods
  • Time Spans fall within / contain other Time Spans [don't ask about the difference with above]
  • Information objects are composed of / form part of other Information Objects
  • Things are composed of / form part of other Things
  • Places fall within / contain other Places
  • Types define typical wholes for /define typical parts of other Types
  • Groups have current or former member of / ... Actors, including other Groups
  • ...

Thus it would be great to have a context that allowed part to be used consistently in the JSON-LD and map to the appropriate predicate in RDF. Currently we have, for example:

{
  "@context": "https://lod.shared-canvas.org/ns/context/1/full.jsonld", 
  "id": "https://lod.shared-canvas.org/example/activity/0", 
  "type": "Activity", 
  "label": "Example Auction", 
  "consists_of": {
    "id": "https://lod.shared-canvas.org/example/activity/0/part/1", 
    "type": "Activity", 
    "label": "Example Auction of Lot"
  }
}

Which expands to this.

And:

{
  "@context": "https://lod.shared-canvas.org/ns/context/1/full.jsonld", 
  "id": "https://lod.shared-canvas.org/example/object/1", 
  "type": "ManMadeObject", 
  "label": "Example Painting", 
  "part": {
    "id": "https://lod.shared-canvas.org/example/object/1/part/1", 
    "type": "PhysicalObject", 
    "label": "Canvas Support"
  }
}

which expands to this (some triples elided here from the full example).

If we could associated part with consists_of when the type is Activity, and with composed_of (currently mapped to part) when the type is ManMadeObject all museum developers' lives would be greatly enriched :)

[Edit: And my life, as I got the wholes/parts around the wrong way in the list first time round!]

@azaroth42
Copy link
Contributor

I think there are a few potential issues to be addressed:

  • If a resource has multiple types, and their scoped contexts are in conflict, how is the conflict resolved? For example, if there was a ManMadeObject that was also an Activity, which predicate does part resolve to? And inversely, if the same predicate has two different mappings in two different classes, which of the keys will be used in the serialization? (Thanks to @gkellogg for this one)
  • If there is a conflict between predicate and class, how is that conflict resolved? For example, if the relationship between Activity and ManMadeObject (being used_specific_object fwiw) also asserted what part should be interpreted as, which takes precedence?
  • Without re-encoding the entire ontology in the context, how can subclasses exhibit the same behavior as their ancestors?

Towards the first, if there is a conflict, then it seems like there are two options:

  1. Pick one of the options at random. This seems undesirable as a source of heisenbug frustration.
  2. Fall back to the prefix:predicate or predicate URL. This (to me) is at least consistent and gives a much clearer indication that there is a conflict. It would also be consistent with the behavior of mixing incompatible types in a language map.

Towards the second ... same as the first? Or use the predicate? This seems like a "don't do that then" moment, to me.

And the last ... re-expressing the differences for every class seems like the only way to go. This has the (IMO) beneficial side effect that context designers will try harder to put everything at the 1.0 compatible top level and only express the differences in scopes.

And as a syntactic proposal, the sub-context could live within the definition of the class's term in the context, and the processor then checks the mappings for both the predicate and the class (conflict resolution as discussed above).

For example:

{
  "@context": { 
  "ns": "http://example.org/ns/",
  "Activity": {
    "@id": "ns:E7_Activity",
    "@type": "@id",
    "@context": {
      "part": {
        "@id": "ns:P9_consists_of",
        "@type": "@id"
      }
    }
  },
  "ManMadeObject": {
    "@id": "ns:E22_Man-Made_Object",
    "@type": "@id",
    "@context": {
      "part": {
        "@id": "ns:P46_is_composed_of",
        "@type": "@id"
      }
    }
  }
  },
  "@id": "http://example.com/myActivity",
  "@type": "Activity",
  "part": [ { ... further sub-activities ... } ],
  "used_specific_object": {
    "@id": "http://example.com/myObject",
    "@type": "ManMadeObject",
    "part": [ { ... further sub-objects ... } ]
  }
}

@dlongley
Copy link
Member

dlongley commented Feb 2, 2017

If a resource has multiple types, and their scoped contexts are in conflict, how is the conflict resolved?

In addition to some kind of last resort automatic resolution, we should also have a way for context designers to specify which type they would prefer to be given preference. Ideally this would be done in a way that could be specified without having to rewrite all of the rules (useful when composing existing contexts together in an array to form a new one).

@gkellogg
Copy link
Member

gkellogg commented Feb 2, 2017

My thought is that contexts found while traversing @type, or it's alias, are handled in order, with any @context definition applied on top of the previous contexts. This implies the following:

When Expanding:

After step 5 (@context key), if element contains a key expanding to @type, then for each term t which is a value of that key:

  1. If t's term definition in active context has a local context, set active context to the result of the Context Processing Algorithm passing active context and t's local context.

When Compacting:

Within step 8.1.2.2 (expanded value must be a @type array), If the term definition for the term found for expanded type has a local context, set active context to the result of the Context Processing Algorithm passing active context and term's local context and update inverse context accordingly.

The only potential for conflict is when going from RDF, where there is no guaranteed sequence of the values of @type, but we could update that algorithm to ensure that types are ordered lexicographically. Personally, I don't think it's a real-world consideration.

@azaroth42
Copy link
Contributor

Personally, I don't think it's a real-world consideration.

👍

@gkellogg
Copy link
Member

gkellogg commented Feb 3, 2017

Pretty simple to implement, I've done it on the develop branch of my processor: ruby-rdf/json-ld@e2eb98b. I'll work on Spec Text and tests tomorrow.

gkellogg added a commit that referenced this issue Feb 4, 2017
@mitar
Copy link
Author

mitar commented Feb 15, 2017

@gkellogg, thank you for your work! Could you maybe show how would the example I made in the initial comment in this issue be transformed using this current change to the spec?

@mitar
Copy link
Author

mitar commented Apr 29, 2017

Hm, this is not yet available in the playground? I tried to play with it.

@mitar
Copy link
Author

mitar commented Apr 29, 2017

I opened #484.

@gkellogg
Copy link
Member

AFAIK, the only processor with these updates is Ruby JSON-LD. I support this in my distiller at http://rdf.greggkellogg.net/distiller, but only for the purpose of deserializing to another RDF serialization, or serializing back from RDF.

If you go so far as to install the Ruby Gem, it will install a "jsonld" executable, that provides full access to compession/expansion/framing and so forth, but from the command line.

@mitar
Copy link
Author

mitar commented Apr 30, 2017

I just tried Ruby Gem and it does not work either. I used this input, expecting this output:

$ jsonld --expand in.json 
Error in -stdin-
/Users/user/.gem/ruby/2.4.0/gems/json-ld-2.1.3/lib/json/ld/context.rb:653:in `create_term_definition': invalid term definition: @context not valid in term definition for JSON-LD 1.0 (JSON::LD::JsonLdError::InvalidTermDefinition)
	from /Users/user/.gem/ruby/2.4.0/gems/json-ld-2.1.3/lib/json/ld/context.rb:466:in `block (2 levels) in parse'
	from /Users/user/.gem/ruby/2.4.0/gems/json-ld-2.1.3/lib/json/ld/context.rb:465:in `each_key'
	from /Users/user/.gem/ruby/2.4.0/gems/json-ld-2.1.3/lib/json/ld/context.rb:465:in `block in parse'
	from /Users/user/.gem/ruby/2.4.0/gems/json-ld-2.1.3/lib/json/ld/context.rb:361:in `each'
	from /Users/user/.gem/ruby/2.4.0/gems/json-ld-2.1.3/lib/json/ld/context.rb:361:in `parse'
	from /Users/user/.gem/ruby/2.4.0/gems/json-ld-2.1.3/lib/json/ld/context.rb:221:in `parse'
	from /Users/user/.gem/ruby/2.4.0/gems/json-ld-2.1.3/lib/json/ld/api.rb:136:in `initialize'
	from /Users/user/.gem/ruby/2.4.0/gems/json-ld-2.1.3/lib/json/ld/api.rb:172:in `new'
	from /Users/user/.gem/ruby/2.4.0/gems/json-ld-2.1.3/lib/json/ld/api.rb:172:in `expand'
	from /Users/user/.gem/ruby/2.4.0/gems/json-ld-2.1.3/bin/jsonld:28:in `run'
	from /Users/user/.gem/ruby/2.4.0/gems/json-ld-2.1.3/bin/jsonld:187:in `block (2 levels) in <top (required)>'
	from /Users/user/.gem/ruby/2.4.0/gems/rdf-2.2.6/lib/rdf/util/file.rb:331:in `open_file'
	from /Users/user/.gem/ruby/2.4.0/gems/json-ld-2.1.3/bin/jsonld:187:in `block in <top (required)>'
	from /Users/user/.gem/ruby/2.4.0/gems/json-ld-2.1.3/bin/jsonld:185:in `each'
	from /Users/user/.gem/ruby/2.4.0/gems/json-ld-2.1.3/bin/jsonld:185:in `<top (required)>'
	from /Users/user/.gem/ruby/2.4.0/bin/jsonld:22:in `load'
	from /Users/user/.gem/ruby/2.4.0/bin/jsonld:22:in `<main>'

@gkellogg
Copy link
Member

It seems that you probably don't have the version announcement added to the context, which is why it complains that @context is not valid in a term definition for JSON-LD 1.0. Try adding "@version": 1.1 to your context definition.

@mitar
Copy link
Author

mitar commented May 1, 2017

Nice. So I converted the initial example I had to this and it looks that it works great:

{
    "@context": {
        "@version": 1.1,
        "@base": "http://127.0.0.1:8000/api/v2/node/",
        "@vocab": "http://127.0.0.1:8000/api/v2/node/?format=vocab#",
        "config/core.location/Location": {
            "@context": {
                "@base": "_:config/core.location/",
                "@vocab": "http://127.0.0.1:8000/api/v2/node/?format=vocab#config/core.location/"
            }
        },
        "config/core.project/Project": {
            "@context": {
                "@base": "_:config/core.project/",
                "@vocab": "http://127.0.0.1:8000/api/v2/node/?format=vocab#config/core.project/"
            }
        }
    },
    "@type": "Node",
    "@id": "000c3d9d-5d7c-492a-8d3b-bb8b922ae35f",
    "config": {
        "core.location": {
            "@id": "123",
            "@type": "config/core.location/Location",
            "timezone": "Europe/Ljubljana",
            "address": "San Juan, Puerto Rico",
            "city": "Ljubljana",
            "country": "SI",
            "geolocation": {
                "@context": "http://geojson.org/geojson-ld/geojson-context.jsonld",
                "type": "Point",
                "coordinates": [
                    -66.073756,
                    18.378287
                ]
            },
            "altitude": 0.0
        },
        "core.project": {
            "@id": "444",
            "@type": "config/core.project/Project",
            "project": "http://127.0.0.1:8000/api/v2/project/2"
        }
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants