diff --git a/README.md b/README.md index 1dd2921..e8e95f8 100644 --- a/README.md +++ b/README.md @@ -222,7 +222,7 @@ Using comment tags, arguments can be customized. |---|--- | `@ignored` | This field will be ignored. | `@default {}` | The `default` attribute of the parameter will be set to `` -| `@range {}` | The `range` attribute of the parameter will be set to ``. You can only use values that fit the type of field. Options: `boolean, int, integer, number, byte, long, float, decimal, double, string`. For example, if your field has the type `number`, you could explicitly mark it as a `float` by using `@range {float}`. See [the documentation](https://componentsjs.readthedocs.io/en/latest/configuration/components/parameters/). +| `@range {}` | The `range` attribute of the parameter will be set to ``. You can only use values that fit the type of field. Options: `json, boolean, int, integer, number, byte, long, float, decimal, double, string`. For example, if your field has the type `number`, you could explicitly mark it as a `float` by using `@range {float}`. See [the documentation](https://componentsjs.readthedocs.io/en/latest/configuration/components/parameters/). #### Examples @@ -265,6 +265,59 @@ Component file: } ``` +**Tagging constructor fields as raw JSON:** + +TypeScript class: +```typescript +export class MyActor { + /** + * @param myValue - Values will be passed as parsed JSON @range {json} + * @param ignoredArg - @ignored + */ + constructor(myValue: any, ignoredArg: string) { + + } +} +``` + +Component file: +```json +{ + "components": [ + { + "parameters": [ + { + "@id": "my-actor#TestClass#myValue", + "range": "rdf:JSON", + "required": false, + "unique": false, + "comment": "Values will be passed as parsed JSON" + } + ], + "constructorArguments": [ + { + "@id": "my-actor#TestClass#myValue" + } + ] + } + ] +} +``` + +When instantiating TestClass as follows, its JSON value will be passed directly into the constructor: +```json +{ + "@id": "ex:myInstance", + "@type": "TestClass", + "myValue": { + "someKey": { + "someOtherKey1": 1, + "someOtherKey2": "abc" + } + } +} +``` + **Tagging interface fields:** TypeScript class: diff --git a/lib/serialize/ComponentConstructor.ts b/lib/serialize/ComponentConstructor.ts index 87ebb1a..2641607 100644 --- a/lib/serialize/ComponentConstructor.ts +++ b/lib/serialize/ComponentConstructor.ts @@ -488,7 +488,7 @@ export class ComponentConstructor { // Fill in required fields const definition: ParameterDefinition = { '@id': fieldId, - range: `xsd:${range}`, + range: range === 'json' ? 'rdf:JSON' : `xsd:${range}`, }; // Fill in optional fields diff --git a/lib/serialize/ContextConstructor.ts b/lib/serialize/ContextConstructor.ts index 268b128..06919fc 100644 --- a/lib/serialize/ContextConstructor.ts +++ b/lib/serialize/ContextConstructor.ts @@ -70,9 +70,12 @@ export class ContextConstructor { // Generate type-scoped context when enabled if (this.typeScopedContexts) { - const typeScopedContext: Record = {}; + const typeScopedContext: Record> = {}; for (const parameter of component.parameters) { - typeScopedContext[parameter['@id'].slice(Math.max(0, component['@id'].length + 1))] = parameter['@id']; + typeScopedContext[parameter['@id'].slice(Math.max(0, component['@id'].length + 1))] = { + '@id': parameter['@id'], + ...parameter.range === 'rdf:JSON' ? { '@type': '@json' } : {}, + }; } ( shortcuts[match[0]])['@context'] = typeScopedContext; } diff --git a/package.json b/package.json index 5e8ffbc..9660a31 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "@types/semver": "^7.3.4", "@typescript-eslint/typescript-estree": "^4.6.1", "comment-parser": "^0.7.6", - "componentsjs": "^4.2.0", + "componentsjs": "^4.3.0", "jsonld-context-parser": "^2.0.2", "lru-cache": "^6.0.0", "minimist": "^1.2.5", diff --git a/test/serialize/ComponentConstructor.test.ts b/test/serialize/ComponentConstructor.test.ts index 487c6de..8fab10f 100644 --- a/test/serialize/ComponentConstructor.test.ts +++ b/test/serialize/ComponentConstructor.test.ts @@ -1732,6 +1732,24 @@ describe('ComponentConstructor', () => { unique: true, }); }); + + it('should construct a JSON parameter definition', () => { + const rangeValue = 'json'; + expect(ctor.constructParameterRaw(context, classReference, { + type: 'field', + name: 'field', + range: { type: 'raw', value: 'string' }, + required: true, + unique: true, + comment: 'Hi', + }, rangeValue, 'mp:a/b/file-param#MyClass_field')).toEqual({ + '@id': 'mp:a/b/file-param#MyClass_field', + comment: 'Hi', + range: 'rdf:JSON', + required: true, + unique: true, + }); + }); }); describe('constructParameterClass', () => { diff --git a/test/serialize/ContextConstructor.test.ts b/test/serialize/ContextConstructor.test.ts index cf59f3f..dc5f2c5 100644 --- a/test/serialize/ContextConstructor.test.ts +++ b/test/serialize/ContextConstructor.test.ts @@ -203,8 +203,80 @@ describe('ContextConstructor', () => { '@id': 'mp:file1#MyClass1', '@prefix': true, '@context': { - param1: 'mp:file1#MyClass1_param1', - param2: 'mp:file1#MyClass1_param2', + param1: { + '@id': 'mp:file1#MyClass1_param1', + }, + param2: { + '@id': 'mp:file1#MyClass1_param2', + }, + }, + }, + MyClass2: { + '@id': 'mp:b/file2#MyClass2', + '@prefix': true, + '@context': {}, + }, + }); + }); + + it('should handle non-empty component definitions when typeScopedContexts is true for JSON ranges', () => { + ctor = new ContextConstructor({ + packageMetadata, + typeScopedContexts: true, + }); + expect(ctor.constructComponentShortcuts({ + '/docs/package/components/file1': { + '@context': [ + 'https://linkedsoftwaredependencies.org/bundles/npm/my-package/context.jsonld', + ], + '@id': 'npmd:my-package', + components: [ + { + '@id': 'mp:file1#MyClass1', + '@type': 'Class', + constructorArguments: [], + parameters: [ + { + '@id': 'mp:file1#MyClass1_param1', + range: 'rdf:JSON', + }, + { + '@id': 'mp:file1#MyClass1_param2', + range: 'rdf:JSON', + }, + ], + requireElement: 'MyClass1', + }, + ], + }, + '/docs/package/components/b/file2': { + '@context': [ + 'https://linkedsoftwaredependencies.org/bundles/npm/my-package/context.jsonld', + ], + '@id': 'npmd:my-package', + components: [ + { + '@id': 'mp:b/file2#MyClass2', + '@type': 'Class', + requireElement: 'MyClass2', + constructorArguments: [], + parameters: [], + }, + ], + }, + })).toEqual({ + MyClass1: { + '@id': 'mp:file1#MyClass1', + '@prefix': true, + '@context': { + param1: { + '@id': 'mp:file1#MyClass1_param1', + '@type': '@json', + }, + param2: { + '@id': 'mp:file1#MyClass1_param2', + '@type': '@json', + }, }, }, MyClass2: {