├── .github └── workflows │ ├── add_prs_and_issues_to_project.yml │ └── ci.yml ├── .gitignore ├── .pr-preview.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Gemfile ├── Gemfile.lock ├── LICENSE.md ├── README.md ├── Rakefile ├── common ├── common.js ├── extract-examples.rb └── jsonld.js ├── index.html └── w3c.json /.github/workflows/add_prs_and_issues_to_project.yml: -------------------------------------------------------------------------------- 1 | name: Add pull requests and issues to projects 2 | 3 | on: 4 | pull_request_target: 5 | types: 6 | - opened 7 | issues: 8 | types: 9 | - opened 10 | 11 | jobs: 12 | add-to-project: 13 | name: Add PR and issues to project 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/add-to-project@v0.4.1 17 | with: 18 | project-url: https://github.com/orgs/w3c/projects/84 19 | github-token: ${{ secrets.ADD_TO_PROJECT_PAT }} 20 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This workflow validates the document for markup and examples. 2 | name: CI 3 | 4 | on: 5 | push: 6 | branches: [ '**' ] 7 | pull_request: 8 | branches: [ main ] 9 | 10 | jobs: 11 | tests: 12 | name: Build and Validate 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: Set up Ruby 17 | uses: ruby/setup-ruby@v1 18 | with: 19 | ruby-version: 3.2 20 | - name: Install dependencies 21 | run: bundle install 22 | 23 | # Validate Examples 24 | - name: Verify examples are consistent 25 | run: bundle exec rake test 26 | 27 | # Validate via ReSpec 28 | # See https://github.com/w3c/spec-prod/blob/main/docs/examples.md 29 | - name: ReSpec Checker 30 | uses: w3c/spec-prod@v2 31 | with: 32 | VALIDATE_LINKS: false 33 | VALIDATE_MARKUP: true 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.byebug_history 2 | -------------------------------------------------------------------------------- /.pr-preview.json: -------------------------------------------------------------------------------- 1 | { 2 | "src_file": "index.html", 3 | "type": "respec" 4 | } 5 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | All documentation, code and communication under this repository are covered by the [W3C Code of Ethics and Professional Conduct](https://www.w3.org/Consortium/cepc/). 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # JSON-LD Working Group 2 | 3 | Contributions to this repository are intended to become part of Recommendation-track documents governed by the 4 | [W3C Patent Policy](https://www.w3.org/Consortium/Patent-Policy-20040205/) and 5 | [Software and Document License](https://www.w3.org/Consortium/Legal/copyright-software). To make substantive contributions to specifications, you must either participate 6 | in the relevant W3C Working Group or make a non-member patent licensing commitment. 7 | 8 | If you are not the sole contributor to a contribution (pull request), please identify all 9 | contributors in the pull request comment. 10 | 11 | To add a contributor (other than yourself, that's automatic), mark them one per line as follows: 12 | 13 | ``` 14 | +@github_username 15 | ``` 16 | 17 | If you added a contributor by mistake, you can remove them in a comment with: 18 | 19 | ``` 20 | -@github_username 21 | ``` 22 | 23 | If you are making a pull request on behalf of someone else but you had no part in designing the 24 | feature, you can remove yourself with the above syntax. 25 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem 'json-ld', github: 'ruby-rdf/json-ld', branch: 'develop' 4 | gem 'rdf-isomorphic', github: 'ruby-rdf/rdf-isomorphic', branch: 'develop' 5 | gem 'linkeddata' 6 | gem 'rdf-trig', github: 'ruby-rdf/rdf-trig', branch: 'develop' 7 | gem 'earl-report' 8 | gem 'nokogiri' 9 | gem 'colorize' 10 | gem 'rake' 11 | gem 'redcarpet' 12 | gem 'byebug' 13 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: https://github.com/ruby-rdf/json-ld.git 3 | revision: fcd3aedd310aedbd61e6e1f1afa1b37d1ffebc2d 4 | branch: develop 5 | specs: 6 | json-ld (3.3.1) 7 | htmlentities (~> 4.3) 8 | json-canonicalization (~> 1.0) 9 | link_header (~> 0.0, >= 0.0.8) 10 | multi_json (~> 1.15) 11 | rack (>= 2.2, < 4) 12 | rdf (~> 3.3) 13 | 14 | GIT 15 | remote: https://github.com/ruby-rdf/rdf-isomorphic.git 16 | revision: 6add041cd0be0ff402285282d7bd4c784e2c89d2 17 | branch: develop 18 | specs: 19 | rdf-isomorphic (3.3.0) 20 | rdf (~> 3.3) 21 | 22 | GIT 23 | remote: https://github.com/ruby-rdf/rdf-trig.git 24 | revision: ccb51eff7022697ada2b30e213f50d667307469d 25 | branch: develop 26 | specs: 27 | rdf-trig (3.3.0) 28 | ebnf (~> 2.4) 29 | rdf (~> 3.3) 30 | rdf-turtle (~> 3.3) 31 | 32 | GEM 33 | remote: https://rubygems.org/ 34 | specs: 35 | addressable (2.8.6) 36 | public_suffix (>= 2.0.2, < 6.0) 37 | bcp47_spec (0.2.1) 38 | builder (3.2.4) 39 | byebug (11.1.3) 40 | colorize (1.1.0) 41 | concurrent-ruby (1.2.3) 42 | connection_pool (2.4.1) 43 | earl-report (0.9.0) 44 | haml (>= 6.1) 45 | json-ld (~> 3.3) 46 | kramdown (~> 2.4) 47 | rdf (~> 3.3) 48 | rdf-turtle (~> 3.3) 49 | rdf-vocab (~> 3.3) 50 | sparql (~> 3.3) 51 | ebnf (2.4.0) 52 | htmlentities (~> 4.3) 53 | rdf (~> 3.3) 54 | scanf (~> 1.0) 55 | sxp (~> 1.3) 56 | unicode-types (~> 1.8) 57 | haml (6.3.0) 58 | temple (>= 0.8.2) 59 | thor 60 | tilt 61 | hamster (3.0.0) 62 | concurrent-ruby (~> 1.0) 63 | htmlentities (4.3.4) 64 | json-canonicalization (1.0.0) 65 | json-ld-preloaded (3.3.0) 66 | json-ld (~> 3.3) 67 | rdf (~> 3.3) 68 | kramdown (2.4.0) 69 | rexml 70 | ld-patch (3.3.0) 71 | ebnf (~> 2.4) 72 | rdf (~> 3.3) 73 | rdf-xsd (~> 3.3) 74 | sparql (~> 3.3) 75 | sxp (~> 1.3) 76 | link_header (0.0.8) 77 | linkeddata (3.3.1) 78 | json-ld (~> 3.3) 79 | json-ld-preloaded (~> 3.3) 80 | ld-patch (~> 3.3) 81 | nokogiri (~> 1.15, >= 1.15.4) 82 | rdf (~> 3.2, >= 3.2.1) 83 | rdf-aggregate-repo (~> 3.2) 84 | rdf-hamster-repo (~> 3.3) 85 | rdf-isomorphic (~> 3.3) 86 | rdf-json (~> 3.3) 87 | rdf-microdata (~> 3.3) 88 | rdf-n3 (~> 3.3) 89 | rdf-normalize (~> 0.7) 90 | rdf-ordered-repo (~> 3.3) 91 | rdf-rdfa (~> 3.3) 92 | rdf-rdfxml (~> 3.3) 93 | rdf-reasoner (~> 0.9) 94 | rdf-tabular (~> 3.3) 95 | rdf-trig (~> 3.3) 96 | rdf-trix (~> 3.3) 97 | rdf-turtle (~> 3.3) 98 | rdf-vocab (~> 3.3) 99 | rdf-xsd (~> 3.3) 100 | shacl (~> 0.4) 101 | shex (~> 0.8) 102 | sparql (~> 3.3) 103 | sparql-client (~> 3.3) 104 | yaml-ld (~> 0.0) 105 | logger (1.6.0) 106 | matrix (0.4.2) 107 | mini_portile2 (2.8.8) 108 | multi_json (1.15.0) 109 | net-http-persistent (4.0.2) 110 | connection_pool (~> 2.2) 111 | nokogiri (1.18.8) 112 | mini_portile2 (~> 2.8.2) 113 | racc (~> 1.4) 114 | nokogiri (1.18.8-aarch64-linux-gnu) 115 | racc (~> 1.4) 116 | nokogiri (1.18.8-arm-linux-gnu) 117 | racc (~> 1.4) 118 | nokogiri (1.18.8-arm64-darwin) 119 | racc (~> 1.4) 120 | nokogiri (1.18.8-x86_64-darwin) 121 | racc (~> 1.4) 122 | nokogiri (1.18.8-x86_64-linux-gnu) 123 | racc (~> 1.4) 124 | psych (5.1.2) 125 | stringio 126 | public_suffix (5.0.4) 127 | racc (1.8.1) 128 | rack (3.0.16) 129 | rake (13.1.0) 130 | rdf (3.3.1) 131 | bcp47_spec (~> 0.2) 132 | link_header (~> 0.0, >= 0.0.8) 133 | rdf-aggregate-repo (3.3.0) 134 | rdf (~> 3.3) 135 | rdf-hamster-repo (3.3.0) 136 | hamster (~> 3.0) 137 | rdf (~> 3.3) 138 | rdf-json (3.3.0) 139 | rdf (~> 3.3) 140 | rdf-microdata (3.3.0) 141 | htmlentities (~> 4.3) 142 | nokogiri (~> 1.15, >= 1.15.4) 143 | rdf (~> 3.3) 144 | rdf-rdfa (~> 3.3) 145 | rdf-xsd (~> 3.3) 146 | rdf-n3 (3.3.0) 147 | ebnf (~> 2.4) 148 | rdf (~> 3.3) 149 | sparql (~> 3.3) 150 | sxp (~> 1.3) 151 | rdf-normalize (0.7.0) 152 | rdf (~> 3.3) 153 | rdf-ordered-repo (3.3.0) 154 | rdf (~> 3.3) 155 | rdf-rdfa (3.3.0) 156 | haml (~> 6.1) 157 | htmlentities (~> 4.3) 158 | rdf (~> 3.3) 159 | rdf-aggregate-repo (~> 3.3) 160 | rdf-vocab (~> 3.3) 161 | rdf-xsd (~> 3.3) 162 | rdf-rdfxml (3.3.0) 163 | builder (~> 3.2, >= 3.2.4) 164 | htmlentities (~> 4.3) 165 | rdf (~> 3.3) 166 | rdf-xsd (~> 3.3) 167 | rdf-reasoner (0.9.0) 168 | rdf (~> 3.3) 169 | rdf-xsd (~> 3.3) 170 | rdf-tabular (3.3.0) 171 | addressable (~> 2.8) 172 | bcp47_spec (~> 0.2) 173 | json-ld (~> 3.3) 174 | rdf (~> 3.3) 175 | rdf-vocab (~> 3.3) 176 | rdf-xsd (~> 3.3) 177 | rdf-trix (3.3.0) 178 | rdf (~> 3.3) 179 | rdf-xsd (~> 3.3) 180 | rdf-turtle (3.3.0) 181 | ebnf (~> 2.4) 182 | rdf (~> 3.3) 183 | rdf-vocab (3.3.0) 184 | rdf (~> 3.3) 185 | rdf-xsd (3.3.0) 186 | rdf (~> 3.3) 187 | rexml (~> 3.2) 188 | redcarpet (3.6.0) 189 | rexml (3.3.9) 190 | scanf (1.0.0) 191 | shacl (0.4.1) 192 | json-ld (~> 3.3) 193 | rdf (~> 3.3) 194 | sparql (~> 3.3) 195 | sxp (~> 1.2) 196 | shex (0.8.0) 197 | ebnf (~> 2.4) 198 | htmlentities (~> 4.3) 199 | json-ld (~> 3.3) 200 | json-ld-preloaded (~> 3.3) 201 | rdf (~> 3.3) 202 | rdf-xsd (~> 3.3) 203 | sparql (~> 3.3) 204 | sxp (~> 1.3) 205 | sparql (3.3.0) 206 | builder (~> 3.2, >= 3.2.4) 207 | ebnf (~> 2.4) 208 | logger (~> 1.5) 209 | rdf (~> 3.3) 210 | rdf-aggregate-repo (~> 3.3) 211 | rdf-xsd (~> 3.3) 212 | sparql-client (~> 3.3) 213 | sxp (~> 1.3) 214 | sparql-client (3.3.0) 215 | net-http-persistent (~> 4.0, >= 4.0.2) 216 | rdf (~> 3.3) 217 | stringio (3.1.0) 218 | sxp (1.3.0) 219 | matrix (~> 0.4) 220 | rdf (~> 3.3) 221 | temple (0.10.3) 222 | thor (1.3.0) 223 | tilt (2.3.0) 224 | unicode-types (1.9.0) 225 | yaml-ld (0.0.3) 226 | json-ld (~> 3.3) 227 | psych (>= 3.3) 228 | rdf (~> 3.3) 229 | rdf-xsd (~> 3.3) 230 | 231 | PLATFORMS 232 | aarch64-linux 233 | arm-linux 234 | arm64-darwin 235 | x86-linux 236 | x86_64-darwin 237 | x86_64-linux 238 | 239 | DEPENDENCIES 240 | byebug 241 | colorize 242 | earl-report 243 | json-ld! 244 | linkeddata 245 | nokogiri 246 | rake 247 | rdf-isomorphic! 248 | rdf-trig! 249 | redcarpet 250 | 251 | BUNDLED WITH 252 | 2.5.6 253 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | All documents in this Repository are licensed by contributors 2 | under the 3 | [W3C Software and Document License](https://www.w3.org/Consortium/Legal/copyright-software). 4 | 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |  3 | 4 | # JSON-LD 1.1 Best Practices 5 | 6 | This is the repository of the Best Practices document for the W3C’s specification on JSON-LD 1.1, developed by the [JSON-LD Working Group](https://www.w3.org/2018/json-ld-wg/). The editors’ draft of the Note can also be [read directly](https://w3c.github.io/json-ld-bp/). 7 | 8 | ## Contributing to the Repository 9 | 10 | Use the standard fork, branch, and pull request workflow to propose changes to the specification. Please make branch names informative—by including the issue or bug number for example. 11 | 12 | Editorial changes that improve the readability of the spec or correct spelling or grammatical mistakes are welcome. 13 | 14 | Please read [CONTRIBUTING.md](CONTRIBUTING.md), about licensing contributions. 15 | 16 | 17 | ## Code of Conduct 18 | 19 | W3C functions under a [code of conduct](https://www.w3.org/Consortium/cepc/). 20 | ) 21 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/setup' 2 | task default: :test 3 | 4 | desc "Test examples in spec files" 5 | task :test do 6 | sh %(bundle exec common/extract-examples.rb index.html) 7 | end 8 | 9 | desc "Extract Examples" 10 | task :examples do 11 | sh %(rm -rf examples yaml) 12 | sh %(bundle exec common/extract-examples.rb --example-dir examples --yaml-dir yaml index.html) 13 | end 14 | 15 | desc "Check HTML" 16 | task :check_html do 17 | require 'nokogiri' 18 | doc = ::Nokogiri::HTML5(File.open("index.html"), max_parse_errors: 1000) 19 | unless doc.errors.empty? 20 | STDERR.puts "Errors found parsing index.html:" 21 | doc.errors.each {|e| STDERR.puts " #{e}"} 22 | exit(1) 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /common/common.js: -------------------------------------------------------------------------------- 1 | /* globals require */ 2 | /* JSON-LD Working Group common spec JavaScript */ 3 | 4 | /* 5 | * Implement tabbed examples. 6 | */ 7 | require(["core/pubsubhub"], (respecEvents) => { 8 | "use strict"; 9 | 10 | respecEvents.sub('end-all', (documentElement) => { 11 | // remove data-cite on where the citation is to ourselves. 12 | const selfDfns = Array.from(document.querySelectorAll("dfn[data-cite^='__SPEC__#']")); 13 | for (const dfn of selfDfns) { 14 | const anchor = dfn.querySelector('a'); 15 | if (anchor) { 16 | const anchorContent = anchor.textContent; 17 | dfn.removeChild(anchor); 18 | dfn.textContent = anchorContent; 19 | } 20 | delete dfn.dataset.cite; 21 | } 22 | 23 | // Update data-cite references to ourselves. 24 | const selfRefs = document.querySelectorAll("a[data-cite^='__SPEC__#']"); 25 | for (const anchor of selfRefs) { 26 | anchor.href= anchor.dataset.cite.replace(/^.*#/,"#"); 27 | delete anchor.dataset.cite; 28 | } 29 | 30 | // 31 | // Remove/hide definitions which are unused 32 | // 1. Find all definitions in a termlist which are not preserved, indexed by data-cite 33 | // 2. Find all references to definitions not in termlist 34 | // 4. Hide definitions which are unreferenced 35 | // 36 | const remoteDfns = []; 37 | document.querySelectorAll(".termlist dfn:not(.preserve)") 38 | .forEach((item, index) => { 39 | if (!selfDfns.includes(item)) { 40 | remoteDfns[item.dataset["cite"]] = item; 41 | } 42 | }); 43 | 44 | // termlist internal references to definitions 45 | const internalRefs = Array.from(document.querySelectorAll(".termlist a[data-cite]")); 46 | 47 | // all references to definitions which are not internal refs 48 | const allRefs = Array.from(document.querySelectorAll("a[data-cite]")) 49 | .filter(e => !internalRefs.includes(e)); 50 | 51 | // Remove terms which are referenced 52 | for (const item of allRefs) { 53 | const cite = item.dataset["cite"]; 54 | // Delete this from remoteDfns, as it is referenced 55 | delete remoteDfns[cite]; 56 | } 57 | 58 | // Now remoteDfns only contains unreferenced terms 59 | for (const item of Object.values(remoteDfns)) { 60 | const dt = item.closest("dt"); 61 | if(dt) { 62 | const dd = dt.nextElementSibling; 63 | // Note, removing messes up some ReSpec references, so hiding instead 64 | // dt.parentNode.removeChild(dt); 65 | // dd.parentNode.removeChild(dd); 66 | dt.hidden = true; 67 | dd.hidden = true; 68 | } 69 | } 70 | 71 | // 72 | // Playground 73 | // 74 | 75 | // Add playground links 76 | for (const link of document.querySelectorAll("a.playground")) { 77 | let pre; 78 | if (link.dataset.resultFor) { 79 | // Referenced pre element 80 | pre = document.querySelector(link.dataset.resultFor + ' > pre'); 81 | } else { 82 | // First pre element of aside 83 | pre = link.closest("aside").querySelector("pre"); 84 | } 85 | const content = unComment(document, pre.textContent) 86 | .replace(/\*\*\*\*/g, '') 87 | .replace(/####([^#]*)####/g, ''); 88 | link.setAttribute('aria-label', 'playground link'); 89 | link.textContent = "Open in playground"; 90 | 91 | // startTab defaults to "expand" 92 | const linkQueryParams = { 93 | startTab: "tab-expand", 94 | "json-ld": content 95 | } 96 | 97 | if (link.dataset.compact !== undefined) { 98 | linkQueryParams.startTab = "tab-" + "compacted"; 99 | linkQueryParams.context = '{}'; 100 | } 101 | 102 | if (link.dataset.flatten !== undefined) { 103 | linkQueryParams.startTab = "tab-" + "flattened"; 104 | linkQueryParams.context = '{}'; 105 | } 106 | 107 | if (link.dataset.frame !== undefined) { 108 | linkQueryParams.startTab = "tab-" + "framed"; 109 | const frameContent = unComment(document, document.querySelector(link.dataset.frame + ' > pre').textContent) 110 | .replace(/\*\*\*\*/g, '') 111 | .replace(/####([^#]*)####/g, ''); 112 | linkQueryParams.frame = frameContent; 113 | } 114 | 115 | // Set context 116 | if (link.dataset.context) { 117 | const contextContent = unComment(document, document.querySelector(link.dataset.context + ' > pre').textContent) 118 | .replace(/\*\*\*\*/g, '') 119 | .replace(/####([^#]*)####/g, ''); 120 | linkQueryParams.context = contextContent; 121 | } 122 | 123 | link.setAttribute('href', 124 | 'https://json-ld.org/playground/#' + 125 | Object.keys(linkQueryParams).map(k => `${encodeURIComponent(k)}=${encodeURIComponent(linkQueryParams[k])}`) 126 | .join('&')); 127 | } 128 | 129 | // Add highlighting and remove comment from pre elements 130 | for (const pre of document.querySelectorAll("pre")) { 131 | // First pre element of aside 132 | const content = pre.innerHTML 133 | .replace(/\*\*\*\*([^*]*)\*\*\*\*/g, '$1') 134 | .replace(/####([^#]*)####/g, '$1'); 135 | pre.innerHTML = content; 136 | } 137 | }); 138 | }); 139 | 140 | function _esc(s) { 141 | return s.replace(/&/g,'&') 142 | .replace(/>/g,'>') 143 | .replace(/"/g,'"') 144 | .replace(/ s.trim()).map(s => s.search(/[^\s]/)); 154 | const leastIndent = Math.min(...indents); 155 | return lines.map(s => s.slice(leastIndent)).join("\n"); 156 | } 157 | 158 | function updateExample(doc, content) { 159 | // perform transformations to make it render and prettier 160 | return _esc(reindent(unComment(doc, content))); 161 | } 162 | 163 | 164 | function unComment(doc, content) { 165 | // perform transformations to make it render and prettier 166 | return content 167 | .replace(//, '') 169 | .replace(/< !\s*-\s*-/g, '') 171 | .replace(/-\s*-\s*>/g, '-->'); 172 | } 173 | -------------------------------------------------------------------------------- /common/extract-examples.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # Extracts examples from a ReSpec document, verifies that example titles are unique. Numbering attempts to replicate that used by ReSpec. Examples in script elements, which are not visibile, may be used for describing the results of related examples 3 | # 4 | # Transformations from JSON-LD 5 | # - @data-frame identifies the title of the frame used to process the example 6 | # - @data-frame-for identifies the source to apply this frame to, verifies that the no errors are encountered 7 | # - @data-context identifies the title of the context used to process the example 8 | # - @data-context-for identifies the source to apply this context to, verifies that the no errors are encountered 9 | # - @data-result-for identifies the title of the source which should result in the content. May be used along with @data-frame or @data-context 10 | # - @data-options indicates the comma-separated option/value pairs to pass to the processor 11 | require 'getoptlong' 12 | require 'json' 13 | require 'json/ld/preloaded' 14 | require 'rdf/isomorphic' 15 | require 'rdf/vocab' 16 | require 'nokogiri' 17 | require 'linkeddata' 18 | require 'fileutils' 19 | require 'colorize' 20 | require 'yaml' 21 | require 'cgi' 22 | 23 | # Define I18N vocabulary 24 | class RDF::Vocab::I18N < RDF::Vocabulary("https://www.w3.org/ns/i18n#"); end unless RDF::Vocab.const_defined?(:I18N) 25 | 26 | # FIXME: This is here until the rdf:JSON is added in RDF.rb 27 | unless RDF::RDFV.properties.include?( RDF.to_uri + 'JSON') 28 | RDF::RDFV.property :JSON, label: "JSON", comment: "JSON datatype" 29 | end 30 | 31 | PREFIXES = { 32 | dc: "http://purl.org/dc/terms/", 33 | dct: "http://purl.org/dc/terms/", 34 | dcterms:"http://purl.org/dc/terms/", 35 | dc11: "http://purl.org/dc/elements/1.1/", 36 | dce: "http://purl.org/dc/elements/1.1/", 37 | cred: "https://w3id.org/credentials#", 38 | ex: "http://example.org/", 39 | foaf: "http://xmlns.com/foaf/0.1/", 40 | prov: "http://www.w3.org/ns/prov#", 41 | rdf: "http://www.w3.org/1999/02/22-rdf-syntax-ns#", 42 | schema: "http://schema.org/", 43 | xsd: "http://www.w3.org/2001/XMLSchema#" 44 | } 45 | example_dir = yaml_dir = verbose = number = line = nil 46 | 47 | opts = GetoptLong.new( 48 | ["--example-dir", GetoptLong::REQUIRED_ARGUMENT], 49 | ["--yaml-dir", GetoptLong::REQUIRED_ARGUMENT], 50 | ["--verbose", '-v', GetoptLong::NO_ARGUMENT], 51 | ["--number", '-n', GetoptLong::REQUIRED_ARGUMENT], 52 | ["--line", '-l', GetoptLong::REQUIRED_ARGUMENT], 53 | ) 54 | opts.each do |opt, arg| 55 | case opt 56 | when '--example-dir' then example_dir = arg && FileUtils::mkdir_p(arg) 57 | when '--yaml-dir' then yaml_dir = arg && FileUtils::mkdir_p(arg) 58 | when '--verbose' then verbose = true 59 | when '--number' then number = arg.to_i 60 | when '--line' then line = arg.to_i 61 | end 62 | end 63 | 64 | num_errors = 0 65 | 66 | # Justify and remove leading and trailing blank lines from str 67 | # Remove highlighting and commented out sections 68 | def justify(str) 69 | str = str. 70 | gsub(/^\s*\s*$/, ''). 72 | gsub('****', ''). 73 | gsub(/####([^#]*)####/, '') 74 | 75 | # remove blank lines 76 | lines = str.split("\n").reject {|s| s =~ /\A\s*\z/} 77 | 78 | # count minimum leading space 79 | leading = lines.map {|s| s.length - s.lstrip.length}.min 80 | 81 | # remove leading blank space 82 | lines.map {|s| s[leading..-1]}.join("\n") 83 | end 84 | 85 | def table_to_dataset(table) 86 | repo = RDF::Repository.new 87 | titles = table.xpath('thead/tr/th/text()').map(&:to_s) 88 | 89 | table.xpath('tbody/tr').each do |row| 90 | gname, subject, predicate, object = nil 91 | row.xpath('td/text()').map(&:to_s).each_with_index do |cell, ndx| 92 | case titles[ndx] 93 | when 'Graph' 94 | gname = case cell 95 | when nil, '', " " then nil 96 | when /^_:/ then RDF::Node.intern(cell[2..-1]) 97 | else RDF::Vocabulary.expand_pname(cell) 98 | end 99 | when 'Subject' 100 | subject = case cell 101 | when /^_:/ then RDF::Node.intern(cell[2..-1]) 102 | else RDF::Vocabulary.expand_pname(cell) 103 | end 104 | when 'Property' 105 | predicate = RDF::Vocabulary.expand_pname(cell.sub("dcterms:", "dc:")) 106 | when 'Value' 107 | object = case cell 108 | when /^_:/ then RDF::Node.intern(cell[2..-1]) 109 | when /^\w+:/ then RDF::Vocabulary.expand_pname(cell.sub("dcterms:", "dc:")) 110 | else RDF::Literal(cell) 111 | end 112 | when 'Value Type' 113 | case cell 114 | when /IRI/, '-', /^\s*$/, " " 115 | else 116 | # We might think something was an IRI, but determine that it's not 117 | dt = RDF::Vocabulary.expand_pname(cell.sub("dcterms:", "dc:")) 118 | object = RDF::Literal(object.to_s, datatype: dt) 119 | end 120 | when 'Language' 121 | case cell 122 | when '-', /^\s*$/ 123 | else 124 | # We might think something was an IRI, but determine that it's not 125 | object = RDF::Literal(object.to_s, language: cell.to_sym) 126 | end 127 | when 'Direction' 128 | case cell 129 | when '-', /^\s*$/ 130 | else 131 | object = RDF::Literal(object.to_s, datatype: RDF::URI("https://www.w3.org/ns/i18n##{object.language}_#{cell}")) 132 | # We might think something was an IRI, but determine that it's not 133 | end 134 | end 135 | end 136 | repo << RDF::Statement.new(subject, predicate, object, graph_name: gname) 137 | end 138 | 139 | repo 140 | end 141 | 142 | def dataset_to_table(repo) 143 | has_graph = !repo.graph_names.empty? 144 | litereals = repo.objects.select(&:literal?) 145 | has_datatype = litereals.any?(&:datatype?) 146 | has_language = litereals.any?(&:language?) 147 | positions = {} 148 | 149 | head = [] 150 | head << "Graph" if has_graph 151 | head += %w(Subject Property Value) 152 | 153 | if has_datatype && has_language 154 | head += ["Value Type", "Language"] 155 | positions = {datatype: (has_graph ? 4 : 3), language: (has_graph ? 5 : 4)} 156 | elsif has_datatype 157 | positions = {datatype: (has_graph ? 4 : 3)} 158 | head << "Value Type" 159 | elsif has_language 160 | positions = {language: (has_graph ? 4 : 3)} 161 | head << "Language" 162 | end 163 | 164 | rows = [] 165 | repo.each_statement do |statement| 166 | row = [] 167 | row << (statement.graph_name || " ").to_s if has_graph 168 | row += statement.to_triple.map do |term| 169 | if term.uri? && RDF::Vocabulary.find_term(term) 170 | RDF::Vocabulary.find_term(term).pname.sub("dc:", "dcterms:") 171 | else 172 | term.to_s 173 | end 174 | end 175 | 176 | if has_datatype 177 | if statement.object.literal? && statement.object.datatype? 178 | row[positions[:datatype]] = RDF::Vocabulary.find_term(statement.object.datatype).pname 179 | else 180 | row[positions[:datatype]] = " " 181 | end 182 | end 183 | 184 | if has_language 185 | if statement.object.literal? && statement.object.language? 186 | row[positions[:language]] = statement.object.language.to_s 187 | else 188 | row[positions[:language]] = " " 189 | end 190 | end 191 | 192 | rows << row 193 | end 194 | 195 | "
#{cell} | "}.join("") + 197 | "
---|
#{cell} | "}.join("") + "
63 | Developers share a common problem: they want a simple, 64 | but extensible way to create an API for a web service that gets the job done, 65 | doesn't design them into a corner, 66 | and allows developers to easily interact with their service without reinventing the wheel. 67 | JSON-LD [[JSON-LD]] has become an important solution, 68 | as it bridges the gap between formally data 69 | and more colloquial JSON interfaces used in APIs from numerous providers. 70 | This guide attempts to define certain best practices for publishing data using JSON-LD, 71 | and interacting with such services. 72 |
73 |This unofficial document has been developed by the 77 | JSON-LD Working Group.
78 |This document describes best practices for generating JSON-LD. 82 | Where normative language is used, it should be considered advisory. 83 |
84 |89 | Coming up with a data format for your API is a common problem. 90 | It can be hard to choose between different data representations, 91 | what names you want to pick, 92 | and even harder if you want to leave room for extensibility. 93 | How do you make all these decisions? 94 | How do you make your API easy to use so people can use short strings to reference common things, 95 | but URLs to enable people to come up with their own so it isn't limiting? 96 | How can you make it easy for other people to add their own data in and make it interoperable? 97 | How do you consume data from other similar apps? 98 | There are technologies that can help you do this. 99 | Now, it isn't perfect – sometimes it won't solve your problem, 100 | but it could maybe solve a lot of them. 101 |
102 |103 | The use of JSON on the web has grown immensely in the last decade, 104 | particularly with the explosion of APIs that eschew XML 105 | in favor of what is considered to be a more developer friendly format 106 | which is directly compatible with JavaScript. 107 | As a result, different sites have chosen their own 108 | proprietary representations for interacting with their sites, 109 | sometimes described using frameworks such as [[swagger]] 110 | which imply a particular URI composition for interacting with their services. 111 | This practice leads to vendor-specific semantic silos, 112 | where the meaning of a particular JSON document makes sense 113 | only by programming directly to the API documentation for a given service. 114 |
115 |116 | show examples from GitHub, Twitter, …? 117 |
118 |119 | As services grow they often introduce incompatible changes leading 120 | to a Version 2 or Version 3 of their API 121 | requiring developers to update client code 122 | to properly handle JSON documents. 123 | In many cases, even small changes can lead to incompatibilities. 124 | Additionally, composing information from multiple APIs becomes problematic, 125 | due to namespace or document format conventions that may differ between API endpoints. 126 | Moreover, the same principles are often repeated across different endpoints 127 | using arbitrary identifiers (name, email, website, etc.); 128 | the community needs to learn to stop repeating itself 129 | (DRY concept) 130 | and reuse common conventions, 131 | although this does not necessarily have to mean using exactly 132 | the same identifiers within the JSON itself (see JSON-LD Context). 133 |
134 |135 | This Note proposes to outline a number of best practices 136 | for API designers or JSON developers 137 | based on the principles of separation of data model from syntax, 138 | the use of discoverable identifiers describing document contents, 139 | and general organizing principles that allow documents 140 | to be machine understandable 141 | (read, interpreted as JSON-LD using Linked Data, 142 | RDF and RDFS vocabulary, and data model principles). 143 |
144 |145 | Key among these is the notion of vocabulary re-use, 146 | so that each endpoint does not need to separately describe 147 | the properties and structure of their JSON documents. 148 | Schema.org provides a great example of doing this, 149 | and includes an extension mechanism 150 | that may already be familiar to API designers. 151 |
152 |153 | JSON-LD is JSON, 154 | and good JSON-LD is first and foremost good JSON. 155 | Since it is also Linked Data, 156 | developers and especially data publishers may find 157 | further useful advice at Data on the Web Best Practices [[dwbp]] 158 | and Best Practices for Publishing Linked Data [[ld-bp]]. 159 |
160 |205 | Publish data using developer friendly JSON 206 | JSON [[json]] is the most popular format for publishing data through APIs; 207 | developers like it, 208 | it is easy to parse, 209 | and it is supported natively in most programming languages. 210 |
211 |For example, the following is reasonably idiomatic JSON which can also be interpreted as JSON-LD, given the appropriate context.
213 |216 | { 217 | "name": "Barack Obama", 218 | "givenName": "Barack", 219 | "familyName": "Obama", 220 | "jobTitle": "44th President of the United States" 221 | } 222 |223 | 224 |
226 | Use a top-level object 227 | JSON documents may be in the form of a object, 228 | or an array of objects. 229 | For most purposes, developers need a single entry point, 230 | so the JSON SHOULD be in the form of a single top-level object. 231 |
232 |236 | Use native values 237 | When possible, property values SHOULD use native JSON datatypes 238 | such as numbers (integer, decimal and floating point) 239 | and booleans (`true` and `false`). 240 |
241 |JSON has a single numeric type, 243 | so using native representation of numbers can lose precision. 244 |
245 | 246 |248 | Assume arrays are unordered 249 | JSON specifies that the values in an array are ordered, 250 | however in many cases arrays are also used for values which are unordered. 251 | Unless specified within the JSON-LD Context, 252 | multiple array values SHOULD be presumed to be unordered. (See Lists and Sets in [[JSON-LD]]).
253 |257 | Use well-known identifiers when describing data 258 | By sticking to basic JSON data expression, 259 | and providing a JSON-LD Context, 260 | all keys used within a JSON document can have unambiguous meaning, 261 | as they bind to URLs which describe their meaning. 262 |
263 |By adding an `@context` entry, 265 | the previous example can now be interpreted as JSON-LD.
266 |269 | { 270 | ****"@context": "http://schema.org"****, 271 | "name": "Barack Obama", 272 | "givenName": "Barack", 273 | "familyName": "Obama", 274 | "jobTitle": "44th President of the United States" 275 | } 276 |277 |
When expanding such a data representation, 279 | a JSON-LD processor replaces these terms with the URIs they expand to 280 | (as well as making property values unambiguous): 281 |
282 |285 | [ 286 | { 287 | "http://schema.org/familyName": [{"@value": "Obama"}], 288 | "http://schema.org/givenName": [{"@value": "Barack"}], 289 | "http://schema.org/jobTitle": [{"@value": "44th President of the United States"}], 290 | "http://schema.org/name": [{"@value": "Barack Obama"}] 291 | } 292 | ] 293 |294 |
Expanded form is not useful as is, 295 | but is necessary for performing further algorithmic transformations 296 | of JSON-LD data and is useful when validating 297 | that JSON-LD entity descriptions say what the publisher means. 298 |
299 |303 | Provide one or more types for JSON objects 304 | Principles of Linked Data dictate that 305 | messages SHOULD be self describing, 306 | which includes adding a `type` to such messages.
307 |Many APIs use JSON messages 309 | where the type of information being conveyed is inferred from the retrieval endpoint. 310 | For example, when retrieving information about a Github Commit, 311 | you might see the following response:
312 |315 | { 316 | "sha": "7638417db6d59f3c431d3e1f261cc637155684cd", 317 | "url": "https://api.github.com/repos/octocat/Hello-World/git/commits/7638417db6d59f3c431d3e1f261cc637155684cd", 318 | "author": { 319 | "date": "2014-11-07T22:01:45Z", 320 | "name": "Scott Chacon", 321 | "email": "schacon@gmail.com" 322 | }, 323 | "committer": { 324 | "date": "2014-11-07T22:01:45Z", 325 | "name": "Scott Chacon", 326 | "email": "schacon@gmail.com" 327 | }, 328 | "message": "added readme, because im a good github citizen\n", 329 | "tree": { 330 | "url": "https://api.github.com/repos/octocat/Hello-World/git/trees/691272480426f78a0138979dd3ce63b77f706feb", 331 | "sha": "691272480426f78a0138979dd3ce63b77f706feb" 332 | }, 333 | "parents": [ 334 | { 335 | "url": "https://api.github.com/repos/octocat/Hello-World/git/commits/1acc419d4d6a9ce985db7be48c6349a0475975b5", 336 | "sha": "1acc419d4d6a9ce985db7be48c6349a0475975b5" 337 | } 338 | ] 339 | } 340 |341 |
The only way to know this is a commit 342 | s to infer it based on the published API documentation, 343 | and the fact that it was returned from an endpoint 344 | defined for retrieving information about commits.
345 |348 | { 349 | "@context": "http://schema.org", 350 | "id": "http://www.wikidata.org/entity/Q76", 351 | ****"type": "Person"****, 352 | "name": "Barack Obama", 353 | "givenName": "Barack", 354 | "familyName": "Obama", 355 | "jobTitle": "44th President of the United States" 356 | } 357 |358 | 359 |
361 | Identify objects with a unique identifier 362 | Entities described in JSON objects often describe web resources having a URL; 363 | entity descriptions SHOULD use an identifier uniquely identifying that entity. 364 | In this case, using the resource location as the identity of the object 365 | is consistent with this practice.
366 |Adding an `id` entry (an alias for `@id`) allows the same person 368 | to be referred to from different locations.
369 |372 | { 373 | "@context": "http://schema.org", 374 | ****"id": "http://www.wikidata.org/entity/Q76"****, 375 | "type": "Person", 376 | "name": "Barack Obama", 377 | "givenName": "Barack", 378 | "familyName": "Obama", 379 | "jobTitle": "44th President of the United States" 380 | } 381 |382 |
There can be ambiguity if an identifier describes the entity description, 383 | or directly represents that entity itself. 384 | As an example, Barack Obama may have a Wikidata entry `http://www.wikidata.org/entity/Q76`, 385 | but it would be a mistake to say that `http://www.wikidata.org/entity/Q76` is Barack Obama. 386 | However, it is common to use this pattern, 387 | particularly if the type of the entity describes a Person, 388 | rather than a WebPage.
389 | 390 |392 | Things not strings 393 | When describing attributes, 394 | entity references SHOULD be used instead of string literals.
395 |In some cases, when describing an attribute of an entity, 397 | it is tempting to using string values which have no independent meaning. 398 | Such values are often used for well known things. 399 | A JSON-LD context can define a term for such values, 400 | which allow them to appear as strings within the message, 401 | but be associated with specific identifiers. 402 | In this case, the property must be defined with type `@vocab` 403 | so that values will be interpreted relative to a vocabulary 404 | rather than the file location.
405 |408 | { 409 | "@context": ["http://schema.org", ****{ 410 | "gender": {"@id": "schema:gender", "@type": "@vocab"} 411 | }****], 412 | "id": "http://www.wikidata.org/entity/Q76", 413 | "type": "Person", 414 | "name": "Barack Obama", 415 | "givenName": "Barack", 416 | "familyName": "Obama", 417 | "jobTitle": "44th President of the United States", 418 | ****"gender": "Male"**** 419 | } 420 |421 | 422 |
424 | Nest referenced inline objects 425 | When multiple related entity descriptions are provided inline, 426 | related entities SHOULD be nested.
427 |For example, when relating one entity to another, 429 | where the related entity is described in the same message:
430 |433 | { 434 | "@context": "http://schema.org", 435 | "id": "http://www.wikidata.org/entity/Q76", 436 | "type": "Person", 437 | "name": "Barack Obama", 438 | "givenName": "Barack", 439 | "familyName": "Obama", 440 | "jobTitle": "44th President of the United States", 441 | ****"spouse": { 442 | "id": "http://www.wikidata.org/entity/Q13133", 443 | "type": "Person", 444 | "name": "Michelle Obama", 445 | "spouse": "http://www.wikidata.org/entity/Q76" 446 | }**** 447 | } 448 |449 |
In this example, the `spouse` relationship is bi-directional, 450 | we have arbitrarily rooted the message with Barack Obama, 451 | and created a symmetric relationship 452 | from Michelle back to Barack by reference, 453 | rather than by nesting.
454 | 455 |457 | When describing an inverse relationship, use a referenced property 458 | FIXME 459 |
460 |468 | External references SHOULD use typed term 469 | When using a property intended to reference another entity, 470 | properties SHOULD be defined to type string values as being references.
471 |For example, the `schema:image` property a `Thing` to an `Image`:
473 |476 | { 477 | "@context": "http://schema.org", 478 | "id": "http://www.wikidata.org/entity/Q76", 479 | "type": "Person", 480 | "name": "Barack Obama", 481 | "givenName": "Barack", 482 | "familyName": "Obama", 483 | "jobTitle": "44th President of the United States", 484 | ****"image": "https://commons.wikimedia.org/wiki/File:President_Barack_Obama.jpg"**** 485 | } 486 |487 |
This will be interpreted as a reference, 488 | rather than a string literal, 489 | because (at the time of publication), 490 | the schema.org JSON-LD Context defines `image` to be of type `@id`:
491 |494 | { 495 | "@context": { 496 | ####...#### 497 | "image": { "@id": "schema:image", ****"@type": "@id"****}####, 498 | ...#### 499 | } 500 | } 501 |502 |
If not defined as such in a remote context, 503 | terms may be (re-) defined in a local context:
504 |507 | { 508 | "@context": ["http://schema.org", ****{ 509 | "image": { "@id": "schema:image", "@type": "@id"} 510 | }****], 511 | "id": "http://www.wikidata.org/entity/Q76", 512 | "type": "Person", 513 | "name": "Barack Obama", 514 | "givenName": "Barack", 515 | "familyName": "Obama", 516 | "jobTitle": "44th President of the United States", 517 | "image": "https://commons.wikimedia.org/wiki/File:President_Barack_Obama.jpg" 518 | } 519 |520 | 521 |
523 | Ordering of array elements 524 | Unless specifically described ordered as an `@list`, 525 | do not depend on the order of elements in an array.
526 |By default, 528 | 529 | arrays in JSON-LD do not convey any ordering of contained elements 530 | . 531 | However, for the processing of contexts, 532 | the ordering of elements in arrays does matter. 533 | When writing array-based contexts, this fact should be kept in mind.
534 |Ordered contexts in arrays allow inheritance 535 | and overriding of context entries. 536 | When processing the following example, 537 | the first `name` entry will be overridden by the second `name` entry.
538 |541 | { 542 | "@context": [ 543 | { 544 | "id": "@id", 545 | "name": "http://schema.org/name" 546 | }, 547 | { 548 | "name": "http://xmlns.com/foaf/0.1/name" 549 | } 550 | ], 551 | "@id": "http://www.wikidata.org/entity/Q76", 552 | ****"name": "Barack Obama"**** 553 | } 554 |555 |
Order is important when processing 556 | protected terms. 557 | While the first example will cause a term redefinition error, 558 | the second example will not throw this error.
559 |562 | { 563 | "@context": [ 564 | { 565 | "@version": 1.1, 566 | "name": { 567 | "@id": "http://schema.org/name", 568 | "@protected": true 569 | } 570 | }, 571 | { 572 | "name": "http://xmlns.com/foaf/0.1/name" 573 | } 574 | ], 575 | "@id": "http://www.wikidata.org/entity/Q76", 576 | ****"name": "Barack Obama"**** 577 | } 578 |579 |
582 | { 583 | "@context": [ 584 | { 585 | "name": "http://xmlns.com/foaf/0.1/name" 586 | }, 587 | { 588 | "@version": 1.1, 589 | "Person": "http://schema.org/Person", 590 | "knows": "http://schema.org/knows", 591 | "name": { 592 | "@id": "http://schema.org/name", 593 | "@protected": true 594 | } 595 | } 596 | ], 597 | "@id": "http://www.wikidata.org/entity/Q76", 598 | ****"name": "Barack Obama"**** 599 | } 600 |601 |
608 | Provide a representation of the entity related by URL 609 | When dereferencing an entity related via a URL, 610 | the location SHOULD provide a representation of that entity.
611 |This practices replicates that described in [[ld-bp]] 613 | 614 | Provide at least one machine-readable representation 615 | of the resource identified by the URI 616 | .
617 |Corollaries to this best practice is that 618 | Cool URIs don't change [[cooluris]], 619 | meaning that URLs describing entities SHOULD be stable 620 | and not depend on variable information. 621 | Also, the URL used to identify an entity is the best API endpoint of that entity 622 | (see also ).
623 |While most use of JSON-LD SHOULD NOT require a client 629 | to change the data representation, 630 | JSON-LD does allow the use of various algorithms 631 | to re-shape a JSON-LD document. 632 | These require the use of the JSON-LD Context, 633 | which is typically represented using a link to a remote document. 634 | Because it is remote, 635 | processing time can be severely impacted 636 | by the time it takes to retrieve this context.
637 |639 | Cache JSON-LD Contexts 640 | Services providing a JSON-LD Context SHOULD 641 | set HTTP cache-control headers to allow liberal caching of such contexts, 642 | and clients SHOULD attempt to use a locally cached version 643 | of these documents.
644 |Typically, libraries used to process JSON-LD documents 646 | should do this for you. 647 | (See also [[json-ld-best-practice-caching]]).
648 |Describe schema.org extension using Role sub-class, 653 | Hydra collections, and LDP collections.
654 |Focus on schema.org?
659 |Describe the use of schema.org Actions and work in Hydra.
664 |Describe anti-pattern of URI construction emphasizing affordances.
665 |Remember that Cool URIs don't change [cooluris]; 670 | correctly modeling data allows changes data representation to be limited.
671 |Describe the use of API keys for controlling API versions, 672 | rather than the use of different versioned URLs.
673 |