diff --git a/code/internal/+openminds/+internal/+mixin/HasControlledInstance.m b/code/internal/+openminds/+internal/+mixin/HasControlledInstance.m index 1da371e1..256107fc 100644 --- a/code/internal/+openminds/+internal/+mixin/HasControlledInstance.m +++ b/code/internal/+openminds/+internal/+mixin/HasControlledInstance.m @@ -16,7 +16,10 @@ instances = openminds.internal.listControlledInstances(type); isMatch = instances.InstanceName==string(name); if any(isMatch) - data = jsondecode(fileread(instances.Filepath(isMatch))); + % Shared JSON-LD decode, so the struct carries at_-form + % keywords; raw jsondecode would mangle @id into x_id. + data = openminds.internal.utility.json.decode( ... + fileread(instances.Filepath(isMatch))); instance = feval(type.ClassName, data); else error(['Could not find data for instance of type "%s" ', ... diff --git a/code/internal/+openminds/+internal/+resolver/InstanceResolver.m b/code/internal/+openminds/+internal/+resolver/InstanceResolver.m index 765bc8dd..6fea68c9 100644 --- a/code/internal/+openminds/+internal/+resolver/InstanceResolver.m +++ b/code/internal/+openminds/+internal/+resolver/InstanceResolver.m @@ -21,8 +21,11 @@ 'Could not find data for instance with IRI "%s"', instance.id) end - % Todo: use the JSON-LD deserializer once it exists - data = jsondecode(fileread(instances.Filepath(isMatch))); + % Decode through the shared JSON-LD utility, so the struct + % carries at_-form keywords like every other decoded document. + % Raw jsondecode would mangle @id into x_id. + data = openminds.internal.utility.json.decode( ... + fileread(instances.Filepath(isMatch))); instance.fromStruct(data); end diff --git a/code/internal/+openminds/+internal/getControlledInstance.m b/code/internal/+openminds/+internal/getControlledInstance.m index 0a8d2b49..8b1d2008 100644 --- a/code/internal/+openminds/+internal/getControlledInstance.m +++ b/code/internal/+openminds/+internal/getControlledInstance.m @@ -60,7 +60,6 @@ end jsonStr = fileread(filePath); - jsonStr = strrep(jsonStr, '''', ''''''); %If character array contains ', need to replace with '' data = openminds.internal.utility.json.decode(jsonStr); end diff --git a/tools/tests/unitTests/ControlledInstanceTest.m b/tools/tests/unitTests/ControlledInstanceTest.m index c659db28..dfb0b991 100644 --- a/tools/tests/unitTests/ControlledInstanceTest.m +++ b/tools/tests/unitTests/ControlledInstanceTest.m @@ -20,6 +20,48 @@ function testGetControlledInstanceLocal(testCase, instanceSpecification, version testCase.assertTrue(contains(jsonStr.at_id, expectedIdUriPrefix)); end + function testApostrophesInDefinitionsAreNotDoubled(testCase) + % Apostrophes inside JSON values survive decoding unchanged, so a + % definition written as 'associative array' reads back with single + % apostrophes. The offline and online readers must agree here, so + % an instance decodes the same way wherever its file came from. + + term = openminds.controlledterms.DataType("associativeArray"); + + testCase.verifyTrue(contains(term.definition, "'associative array'"), ... + 'The definition should contain the apostrophes as written in the file.') + testCase.verifyFalse(contains(term.definition, "''"), ... + 'Apostrophes inside values must not be doubled by the reader.') + end + + function testResolvedInstanceCarriesFileContent(testCase) + % Resolving a controlled instance IRI populates the instance from + % the library file, keeping the identifier and enriching values. + + IRI = "https://openminds.om-i.org/instances/dataType/associativeArray"; + instance = openminds.instanceFromIRI(IRI); + + testCase.verifyEqual(string(instance.id), IRI) + testCase.verifyEqual(instance.name, "associative array") + testCase.verifyFalse(contains(instance.definition, "''")) + end + + function testFromNameCarriesFileIdentifier(testCase) + % A controlled instance built by name through the mixin gets its + % identifier from the library file rather than a generated one. + + instanceNames = openminds.core.data.ContentType.listInstances(); + testCase.assumeNotEmpty(instanceNames, ... + 'No controlled ContentType instances available locally.') + + instance = openminds.core.data.ContentType.fromName(instanceNames(1)); + + testCase.verifyTrue(startsWith(string(instance.id), ... + "https://openminds.om-i.org/instances/contentTypes/"), ... + 'The identifier should come from the library file.') + testCase.verifyNotEqual(string(instance.name), "") + end + function testGetControlledInstanceRemote(testCase, instanceSpecification, versionNumber) jsonStr = openminds.internal.getControlledInstance(... instanceSpecification{:}, versionNumber, "FileSource", "github");