├── .github ├── dependabot.yaml └── workflows │ └── test.yaml ├── .gitignore ├── FsDocumentLoader.js ├── LICENSE.md ├── README.md ├── index.js ├── lib └── ParserStream.js ├── package.json └── test ├── FsDocumentLoader.test.js ├── support └── example.org.json └── test.js /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: daily 7 | - package-ecosystem: npm 8 | directory: / 9 | schedule: 10 | interval: daily 11 | versioning-strategy: increase-if-necessary 12 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: 3 | - pull_request 4 | - push 5 | jobs: 6 | test: 7 | runs-on: ubuntu-24.04 8 | strategy: 9 | matrix: 10 | node: 11 | - '18' 12 | - '20' 13 | - '22' 14 | - '23' 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: actions/setup-node@v4 18 | with: 19 | node-version: ${{ matrix.node }} 20 | - run: npm install 21 | - run: npm test 22 | - uses: coverallsapp/github-action@v2 23 | with: 24 | flag-name: run Node v${{ matrix.node }} 25 | github-token: ${{ secrets.GITHUB_TOKEN }} 26 | parallel: true 27 | finally: 28 | needs: test 29 | runs-on: ubuntu-24.04 30 | steps: 31 | - uses: coverallsapp/github-action@v2 32 | with: 33 | github-token: ${{ secrets.GITHUB_TOKEN }} 34 | parallel-finished: true 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage 2 | node_modules 3 | package-lock.json 4 | -------------------------------------------------------------------------------- /FsDocumentLoader.js: -------------------------------------------------------------------------------- 1 | import { readFile } from 'fs/promises' 2 | 3 | class FsDocumentLoader { 4 | constructor (map) { 5 | const entries = (map.entries && map.entries()) || Object.entries(map) 6 | this.map = new Map([...entries]) 7 | } 8 | 9 | async load (url) { 10 | const path = this.map.get(url) 11 | 12 | if (!path) { 13 | throw new Error(`unknown context url: ${url}`) 14 | } 15 | 16 | const content = (await readFile(path)).toString() 17 | 18 | return JSON.parse(content) 19 | } 20 | } 21 | 22 | export default FsDocumentLoader 23 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Thomas Bergwinkl 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @rdfjs/parser-jsonld 2 | [![build status](https://img.shields.io/github/actions/workflow/status/rdfjs-base/parser-jsonld/test.yaml?branch=master)](https://github.com/rdfjs-base/parser-jsonld/actions/workflows/test.yaml) 3 | [![npm version](https://img.shields.io/npm/v/@rdfjs/parser-jsonld.svg)](https://www.npmjs.com/package/@rdfjs/parser-jsonld) 4 | 5 | JSON-LD parser that implements the [RDF/JS Sink interface](http://rdf.js.org/) using [jsonld-streaming-parser](https://github.com/rubensworks/jsonld-streaming-parser.js). 6 | 7 | ## Usage 8 | 9 | Use the following command to add the package as a dependency to your project: 10 | 11 | ```bash 12 | npm install @rdfjs/data-model --save 13 | ``` 14 | 15 | The package exports the parser as a class, so an instance must be created before it can be used. 16 | The `.import` method, as defined in the [RDF/JS specification](http://rdf.js.org/#sink-interface), must be called to do the actual parsing. 17 | It expects a JSON string stream or a stream which emits a single object. 18 | The method will return a stream which emits the parsed quads. 19 | 20 | The constructor accepts an `options` object with the following optional keys: 21 | 22 | - `baseIRI`: Allows passing the base IRI manually to the `jsonld-streaming-parser` library. 23 | - `context`: Allows passing a JSON-LD context manually to the `jsonld-streaming-parser` library. 24 | - `factory`: Use an alternative RDF/JS data factory. 25 | By default the [reference implementation](https://github.com/rdfjs-base/data-model/) us used. 26 | 27 | It's also possible to pass options as second argument to the `.import` method. 28 | The options from the constructor and the `.import` method will be merged together. 29 | 30 | ### Example 31 | 32 | This example shows how to create a parser instance and how to feed it with a stream from a string. 33 | The parsed quads are written to the console. 34 | 35 | ```javascript 36 | import ParserJsonld from '@rdfjs/parser-jsonld' 37 | import { Readable } from 'stream' 38 | 39 | const parserJsonld = new ParserJsonld() 40 | 41 | const input = new Readable({ 42 | read: () => { 43 | input.push(`{ 44 | "@context": "http://schema.org/", 45 | "@type": "Person", 46 | "name": "Jane Doe", 47 | "jobTitle": "Professor", 48 | "telephone": "(425) 123-4567", 49 | "url": "http://www.janedoe.com" 50 | }`) 51 | input.push(null) 52 | } 53 | }) 54 | 55 | const output = parserJsonld.import(input) 56 | 57 | output.on('data', quad => { 58 | console.log(`${quad.subject.value} - ${quad.predicate.value} - ${quad.object.value}`) 59 | }) 60 | ``` 61 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import Sink from '@rdfjs/sink' 2 | import ParserStream from './lib/ParserStream.js' 3 | 4 | class Parser extends Sink { 5 | constructor (options) { 6 | super(ParserStream, options) 7 | } 8 | } 9 | 10 | export default Parser 11 | -------------------------------------------------------------------------------- /lib/ParserStream.js: -------------------------------------------------------------------------------- 1 | import rdf from '@rdfjs/data-model' 2 | import toReadable from 'duplex-to/readable.js' 3 | import { JsonLdParser } from 'jsonld-streaming-parser' 4 | import { Transform } from 'readable-stream' 5 | 6 | const relativeIriProtocol = 'null:' 7 | 8 | function termCleanup (factory) { 9 | return term => { 10 | if (term.termType !== 'NamedNode') { 11 | return null 12 | } 13 | 14 | if (!term.value.startsWith(relativeIriProtocol)) { 15 | return null 16 | } 17 | 18 | // remove dummy protocol workaround for relative IRIs 19 | return factory.namedNode(term.value.slice(relativeIriProtocol.length)) 20 | } 21 | } 22 | 23 | function quadCleanup (factory) { 24 | const cleanup = termCleanup(factory) 25 | 26 | return quad => { 27 | const subject = cleanup(quad.subject) 28 | const predicate = cleanup(quad.predicate) 29 | const object = cleanup(quad.object) 30 | const graph = cleanup(quad.graph) 31 | 32 | if (subject || predicate || object || graph) { 33 | return factory.quad( 34 | subject || quad.subject, 35 | predicate || quad.predicate, 36 | object || quad.object, 37 | graph || quad.graph 38 | ) 39 | } 40 | 41 | return quad 42 | } 43 | } 44 | 45 | class ParserStream { 46 | constructor (input, { baseIRI = relativeIriProtocol, context = null, documentLoader, factory = rdf } = {}) { 47 | const parser = new JsonLdParser({ 48 | baseIRI, 49 | context, 50 | dataFactory: factory, 51 | documentLoader, 52 | streamingProfile: false 53 | }) 54 | 55 | input.pipe(parser) 56 | 57 | const cleanup = quadCleanup(factory) 58 | 59 | const transform = new Transform({ 60 | objectMode: true, 61 | transform: (quad, encoding, callback) => { 62 | callback(null, cleanup(quad)) 63 | } 64 | }) 65 | 66 | parser.on('context', context => { 67 | Object.entries(context).forEach(([prefix, iri]) => { 68 | transform.emit('prefix', prefix, factory.namedNode(iri)) 69 | }) 70 | }) 71 | parser.on('error', err => transform.destroy(err)) 72 | parser.pipe(transform) 73 | 74 | return toReadable(transform) 75 | } 76 | } 77 | 78 | export default ParserStream 79 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@rdfjs/parser-jsonld", 3 | "version": "2.1.3", 4 | "description": "JSON-LD parser that implements the RDFJS Sink interface using jsonld.js", 5 | "type": "module", 6 | "main": "index.js", 7 | "scripts": { 8 | "test": "stricter-standard && c8 --reporter=lcov --reporter=text mocha" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/rdfjs-base/parser-jsonld.git" 13 | }, 14 | "keywords": [ 15 | "rdf", 16 | "rdfjs", 17 | "parser", 18 | "jsonld" 19 | ], 20 | "author": "Thomas Bergwinkl (https://www.bergnet.org/people/bergi/card#me)", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/rdfjs-base/parser-jsonld/issues" 24 | }, 25 | "homepage": "https://github.com/rdfjs-base/parser-jsonld", 26 | "dependencies": { 27 | "@rdfjs/data-model": "^2.0.2", 28 | "@rdfjs/sink": "^2.0.1", 29 | "duplex-to": "^2.0.0", 30 | "jsonld-streaming-parser": "^5.0.0", 31 | "readable-stream": "^4.5.2" 32 | }, 33 | "devDependencies": { 34 | "assert": "^2.1.0", 35 | "c8": "^10.0.0", 36 | "is-stream": "^4.0.1", 37 | "mocha": "^11.0.1", 38 | "stream-chunks": "^1.0.0", 39 | "stricter-standard": "^0.3.0" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/FsDocumentLoader.test.js: -------------------------------------------------------------------------------- 1 | import { deepStrictEqual, rejects, strictEqual } from 'assert' 2 | import { readFile } from 'fs/promises' 3 | import { describe, it } from 'mocha' 4 | import FsDocumentLoader from '../FsDocumentLoader.js' 5 | 6 | describe('FsDocumentLoader', () => { 7 | it('should be a constructor', async () => { 8 | strictEqual(typeof FsDocumentLoader, 'function') 9 | }) 10 | 11 | it('should assign a given object to the map', () => { 12 | const map = { 13 | 'http://example.org/': 'example.org.json', 14 | 'http://example.com/': 'example.com.json' 15 | } 16 | 17 | const documentLoader = new FsDocumentLoader(map) 18 | 19 | strictEqual(documentLoader.map.size, 2) 20 | 21 | for (const [key, value] of Object.entries(map)) { 22 | strictEqual(documentLoader.map.get(key), value) 23 | } 24 | }) 25 | 26 | it('should assign a given map to the map', () => { 27 | const map = new Map([ 28 | ['http://example.org/', 'example.org.json'], 29 | ['http://example.com/', 'example.com.json'] 30 | ]) 31 | 32 | const documentLoader = new FsDocumentLoader(map) 33 | 34 | strictEqual(documentLoader.map.size, 2) 35 | deepStrictEqual(documentLoader.map, map) 36 | }) 37 | 38 | describe('.load', () => { 39 | it('should be a method', () => { 40 | const documentLoader = new FsDocumentLoader({}) 41 | 42 | strictEqual(typeof documentLoader.load, 'function') 43 | }) 44 | 45 | it('should return the context in the map as object', async () => { 46 | const url = 'http://example.org/' 47 | const path = 'test/support/example.org.json' 48 | 49 | const map = {} 50 | map[url] = path 51 | 52 | const documentLoader = new FsDocumentLoader(map) 53 | const expected = JSON.parse((await readFile(path)).toString()) 54 | 55 | const actual = await documentLoader.load(url) 56 | 57 | deepStrictEqual(actual, expected) 58 | }) 59 | 60 | it('should throw an error if the use context is not given in the map', async () => { 61 | const documentLoader = new FsDocumentLoader({}) 62 | 63 | await rejects(async () => { 64 | await documentLoader.load('http://example.com/') 65 | }) 66 | }) 67 | }) 68 | }) 69 | -------------------------------------------------------------------------------- /test/support/example.org.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": { 3 | "@vocab": "http://example.org/" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | import { rejects, strictEqual } from 'assert' 2 | import sinkTest from '@rdfjs/sink/test/index.js' 3 | import { isReadableStream, isWritableStream } from 'is-stream' 4 | import { describe, it } from 'mocha' 5 | import { Readable } from 'readable-stream' 6 | import chunks from 'stream-chunks/chunks.js' 7 | import FsDocumentLoader from '../FsDocumentLoader.js' 8 | import JSONLDParser from '../index.js' 9 | 10 | describe('@rdfjs/parser-jsond', () => { 11 | sinkTest(JSONLDParser, { readable: true }) 12 | 13 | it('should return a readable stream', async () => { 14 | const parser = new JSONLDParser() 15 | const stream = parser.import(Readable.from('{}')) 16 | 17 | strictEqual(isReadableStream(stream), true) 18 | strictEqual(isWritableStream(stream), false) 19 | 20 | await chunks(stream) 21 | }) 22 | 23 | it('should support Named Node subjects', async () => { 24 | const example = JSON.stringify({ 25 | '@id': 'http://example.org/subject', 26 | 'http://example.org/predicate': 'object' 27 | }) 28 | 29 | const parser = new JSONLDParser() 30 | const stream = parser.import(Readable.from(example)) 31 | 32 | const output = await chunks(stream) 33 | 34 | strictEqual(output.length, 1) 35 | strictEqual(output[0].subject.termType, 'NamedNode') 36 | strictEqual(output[0].subject.value, 'http://example.org/subject') 37 | }) 38 | 39 | it('should support empty Named Node subjects', async () => { 40 | const example = JSON.stringify({ 41 | '@id': '', 42 | 'http://example.org/predicate': 'object' 43 | }) 44 | 45 | const parser = new JSONLDParser() 46 | const stream = parser.import(Readable.from(example)) 47 | 48 | const output = await chunks(stream) 49 | 50 | strictEqual(output.length, 1) 51 | strictEqual(output[0].subject.termType, 'NamedNode') 52 | strictEqual(output[0].subject.value, '') 53 | strictEqual(output[0].predicate.termType, 'NamedNode') 54 | strictEqual(output[0].predicate.value, 'http://example.org/predicate') 55 | strictEqual(output[0].object.termType, 'Literal') 56 | strictEqual(output[0].object.value, 'object') 57 | }) 58 | 59 | it('should support relative Named Node subjects', async () => { 60 | const example = JSON.stringify({ 61 | '@id': 'relative', 62 | 'http://example.org/predicate': 'object' 63 | }) 64 | 65 | const parser = new JSONLDParser() 66 | const stream = parser.import(Readable.from(example)) 67 | 68 | const output = await chunks(stream) 69 | 70 | strictEqual(output.length, 1) 71 | strictEqual(output[0].subject.termType, 'NamedNode') 72 | strictEqual(output[0].subject.value, 'relative') 73 | }) 74 | 75 | it('should support Blank Node subjects', async () => { 76 | const example = JSON.stringify({ 77 | 'http://example.org/predicate': 'object' 78 | }) 79 | 80 | const parser = new JSONLDParser() 81 | const stream = parser.import(Readable.from(example)) 82 | 83 | const output = await chunks(stream) 84 | 85 | strictEqual(output.length, 1) 86 | strictEqual(output[0].subject.termType, 'BlankNode') 87 | }) 88 | 89 | it('should parse the predicate', async () => { 90 | const example = JSON.stringify({ 91 | 'http://example.org/predicate': 'object' 92 | }) 93 | 94 | const parser = new JSONLDParser() 95 | const stream = parser.import(Readable.from(example)) 96 | 97 | const output = await chunks(stream) 98 | 99 | strictEqual(output.length, 1) 100 | strictEqual(output[0].predicate.termType, 'NamedNode') 101 | strictEqual(output[0].predicate.value, 'http://example.org/predicate') 102 | }) 103 | 104 | it('should parse a Named Node object', async () => { 105 | const example = JSON.stringify({ 106 | 'http://example.org/predicate': { 107 | '@id': 'http://example.org/object' 108 | } 109 | }) 110 | 111 | const parser = new JSONLDParser() 112 | const stream = parser.import(Readable.from(example)) 113 | 114 | const output = await chunks(stream) 115 | 116 | strictEqual(output.length, 1) 117 | strictEqual(output[0].object.termType, 'NamedNode') 118 | strictEqual(output[0].object.value, 'http://example.org/object') 119 | }) 120 | 121 | it('should parse a Blank Node object', async () => { 122 | const example = JSON.stringify({ 123 | 'http://example.org/predicate': {} 124 | }) 125 | 126 | const parser = new JSONLDParser() 127 | const stream = parser.import(Readable.from(example)) 128 | 129 | const output = await chunks(stream) 130 | 131 | strictEqual(output.length, 1) 132 | strictEqual(output[0].object.termType, 'BlankNode') 133 | strictEqual(output[0].object.value.startsWith('_:'), false) 134 | }) 135 | 136 | it('should keep Blank Node object mapping', async () => { 137 | const example = JSON.stringify({ 138 | 'http://example.org/predicate1': { '@id': '_:b0' }, 139 | 'http://example.org/predicate2': { '@id': '_:b0' } 140 | }) 141 | 142 | const parser = new JSONLDParser() 143 | const stream = parser.import(Readable.from(example)) 144 | 145 | const output = await chunks(stream) 146 | 147 | strictEqual(output.length, 2) 148 | strictEqual(output[0].object.equals(output[1].object), true) 149 | }) 150 | 151 | it('should parse a Literal object', async () => { 152 | const example = JSON.stringify({ 153 | 'http://example.org/predicate': { 154 | '@value': 'object' 155 | } 156 | }) 157 | 158 | const parser = new JSONLDParser() 159 | const stream = parser.import(Readable.from(example)) 160 | 161 | const output = await chunks(stream) 162 | 163 | strictEqual(output.length, 1) 164 | strictEqual(output[0].object.termType, 'Literal') 165 | strictEqual(output[0].object.value, 'object') 166 | strictEqual(output[0].object.language, '') 167 | strictEqual(output[0].object.datatype.value, 'http://www.w3.org/2001/XMLSchema#string') 168 | }) 169 | 170 | it('should parse the language of a Literal object', async () => { 171 | const example = JSON.stringify({ 172 | 'http://example.org/predicate': { 173 | '@value': 'object', 174 | '@language': 'en' 175 | } 176 | }) 177 | 178 | const parser = new JSONLDParser() 179 | const stream = parser.import(Readable.from(example)) 180 | 181 | const output = await chunks(stream) 182 | 183 | strictEqual(output.length, 1) 184 | strictEqual(output[0].object.termType, 'Literal') 185 | strictEqual(output[0].object.value, 'object') 186 | strictEqual(output[0].object.language, 'en') 187 | strictEqual(output[0].object.datatype.value, 'http://www.w3.org/1999/02/22-rdf-syntax-ns#langString') 188 | }) 189 | 190 | it('should parse the datatype of a Literal object', async () => { 191 | const example = JSON.stringify({ 192 | 'http://example.org/predicate': { 193 | '@value': 'object', 194 | '@type': 'http://example.org/datatype' 195 | } 196 | }) 197 | 198 | const parser = new JSONLDParser() 199 | const stream = parser.import(Readable.from(example)) 200 | 201 | const output = await chunks(stream) 202 | 203 | strictEqual(output.length, 1) 204 | strictEqual(output[0].object.termType, 'Literal') 205 | strictEqual(output[0].object.value, 'object') 206 | strictEqual(output[0].object.language, '') 207 | strictEqual(output[0].object.datatype.value, 'http://example.org/datatype') 208 | }) 209 | 210 | it('should parse the datatype of a Literal object into a full featured Literal', async () => { 211 | const example = JSON.stringify({ 212 | 'http://example.org/predicate': { 213 | '@value': 'object', 214 | '@type': 'http://example.org/datatype' 215 | } 216 | }) 217 | 218 | const parser = new JSONLDParser() 219 | const stream = parser.import(Readable.from(example)) 220 | 221 | const output = await chunks(stream) 222 | 223 | strictEqual(output.length, 1) 224 | strictEqual(typeof output[0].object.datatype.equals, 'function') 225 | }) 226 | 227 | it('should use the default graph if none was given', async () => { 228 | const example = JSON.stringify({ 229 | 'http://example.org/predicate': 'object' 230 | }) 231 | 232 | const parser = new JSONLDParser() 233 | const stream = parser.import(Readable.from(example)) 234 | 235 | const output = await chunks(stream) 236 | 237 | strictEqual(output.length, 1) 238 | strictEqual(output[0].graph.termType, 'DefaultGraph') 239 | }) 240 | 241 | it('should parse graph', async () => { 242 | const example = JSON.stringify({ 243 | '@id': 'http://example.org/graph', 244 | '@graph': { 245 | 'http://example.org/predicate': 'object' 246 | } 247 | }) 248 | 249 | const parser = new JSONLDParser() 250 | const stream = parser.import(Readable.from(example)) 251 | 252 | const output = await chunks(stream) 253 | 254 | strictEqual(output.length, 1) 255 | strictEqual(output[0].graph.termType, 'NamedNode') 256 | strictEqual(output[0].graph.value, 'http://example.org/graph') 257 | }) 258 | 259 | it('should use baseIRI option', async () => { 260 | const example = JSON.stringify({ 261 | '@id': 'subject', 262 | 'http://example.org/predicate': 'object' 263 | }) 264 | 265 | const parser = new JSONLDParser({ baseIRI: 'http://example.org/' }) 266 | const stream = parser.import(Readable.from(example)) 267 | 268 | const output = await chunks(stream) 269 | 270 | strictEqual(output.length, 1) 271 | strictEqual(output[0].subject.termType, 'NamedNode') 272 | strictEqual(output[0].subject.value, 'http://example.org/subject') 273 | }) 274 | 275 | it('should use context option', async () => { 276 | const example = JSON.stringify({ 277 | '@id': 'subject', 278 | predicate: 'object' 279 | }) 280 | 281 | const context = { 282 | '@vocab': 'http://example.org/' 283 | } 284 | 285 | const parser = new JSONLDParser({ 286 | baseIRI: 'http://example.org/', 287 | context 288 | }) 289 | const stream = parser.import(Readable.from(example)) 290 | 291 | const output = await chunks(stream) 292 | 293 | strictEqual(output.length, 1) 294 | strictEqual(output[0].subject.termType, 'NamedNode') 295 | strictEqual(output[0].subject.value, 'http://example.org/subject') 296 | }) 297 | 298 | it('should forward errors from the input stream', async () => { 299 | const input = new Readable({ 300 | read: () => { 301 | setTimeout(() => { 302 | input.destroy(new Error('test')) 303 | }, 0) 304 | } 305 | }) 306 | const parser = new JSONLDParser() 307 | const stream = parser.import(input) 308 | 309 | await rejects(chunks(stream, true)) 310 | }) 311 | 312 | it('should throw an error if JSON is invalid', async () => { 313 | const parser = new JSONLDParser() 314 | const stream = parser.import(Readable.from('{')) 315 | 316 | await rejects(chunks(stream, true)) 317 | }) 318 | 319 | it('should throw an error if JSON-LD is invalid', async () => { 320 | const example = JSON.stringify({ 321 | '@context': 'object' 322 | }) 323 | 324 | const parser = new JSONLDParser() 325 | const stream = parser.import(Readable.from(example)) 326 | 327 | await rejects(chunks(stream, true)) 328 | }) 329 | 330 | it('should emit a prefix event for each context entry', async () => { 331 | const example = JSON.stringify({ 332 | '@context': { 333 | ex1: 'http://example.org/1', 334 | ex2: 'http://example.org/2' 335 | } 336 | }) 337 | 338 | const prefixes = {} 339 | 340 | const parser = new JSONLDParser() 341 | const stream = parser.import(Readable.from(example)) 342 | 343 | stream.on('prefix', (prefix, namespace) => { 344 | prefixes[prefix] = namespace 345 | }) 346 | 347 | await chunks(stream, true) 348 | 349 | strictEqual(prefixes.ex1.value, 'http://example.org/1') 350 | strictEqual(prefixes.ex2.value, 'http://example.org/2') 351 | }) 352 | 353 | it('should use a given documentLoader', async () => { 354 | const example = JSON.stringify({ 355 | '@context': 'http://example.org/', 356 | '@id': 'subject', 357 | predicate: 'object' 358 | }) 359 | 360 | const documentLoader = new FsDocumentLoader({ 361 | 'http://example.org/': 'test/support/example.org.json' 362 | }) 363 | 364 | const parser = new JSONLDParser({ documentLoader }) 365 | const stream = parser.import(Readable.from(example)) 366 | 367 | const output = await chunks(stream) 368 | 369 | strictEqual(output.length, 1) 370 | strictEqual(output[0].predicate.termType, 'NamedNode') 371 | strictEqual(output[0].predicate.value, 'http://example.org/predicate') 372 | }) 373 | }) 374 | --------------------------------------------------------------------------------