Skip to content

Commit f067605

Browse files
committed
Accept a scalar when a list is expected.
When deserialising, if we expect a list a scalars, we already accept a single scalar instead (this is the behaviour recommended by the LinkML spec). This commit implements the same behaviour in two other cases: (1) when we expect a list of identifiers (for a multivalued, non-inlined slot) and (2) when we expect a list of type designators.
1 parent dd10f2c commit f067605

2 files changed

Lines changed: 47 additions & 9 deletions

File tree

core/src/main/java/org/incenp/linkml/core/ObjectConverter.java

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ public Object convert(Map<String, Object> raw, ConverterContext ctx) throws Link
169169

170170
if ( designatorSlot.isMultivalued() ) {
171171
ArrayList<String> designatorNames = new ArrayList<>();
172-
for ( Object rawDesignator : toList(designator) ) {
172+
for ( Object rawDesignator : toList(designator, true) ) {
173173
designatorNames.add(designatorConverter.convert(rawDesignator, ctx).toString());
174174
}
175175
designatedClass = resolver.resolve(designatorNames, klass);
@@ -293,7 +293,7 @@ protected String getGlobalIdentifier(Object raw, ConverterContext ctx) throws Li
293293
protected List<String> getGlobalIdentifierList(Object raw, ConverterContext ctx) throws LinkMLRuntimeException {
294294
IConverter conv = ctx.getConverter(klass.getIdentifierSlot());
295295
ArrayList<String> list = new ArrayList<>();
296-
for ( Object rawItem : toList(raw) ) {
296+
for ( Object rawItem : toList(raw, true) ) {
297297
list.add(conv.convert(rawItem, ctx).toString());
298298
}
299299
return list;
@@ -366,7 +366,7 @@ public Map<String, Object> serialise(Object object, boolean withIdentifier, Conv
366366
public Object serialiseForSlot(Object object, Slot slot, ConverterContext ctx) throws LinkMLRuntimeException {
367367
InliningMode inlining = slot.getInliningMode();
368368
if ( slot.isMultivalued() ) {
369-
List<Object> items = toList(object);
369+
List<Object> items = toList(object, false);
370370
if ( inlining == InliningMode.LIST ) {
371371
List<Object> list = new ArrayList<>();
372372
for ( Object item : items ) {
@@ -445,18 +445,33 @@ protected Map<String, Object> toMap(Object value) throws LinkMLRuntimeException
445445
}
446446

447447
/**
448-
* Checks that a raw object is a list, and casts it as such.
448+
* Turns a raw object into a list.
449+
* <p>
450+
* This checks that the given object is a list, and casts it as such. If it is
451+
* not a list, then an exception is thrown, unless the <code>wrap</code>
452+
* argument is set to <code>true</code>, in which case the value is wrapped into
453+
* a single item list.
449454
*
450455
* @param value The raw object to cast.
451-
* @return The input object, cast into a list.
452-
* @throws LinkMLRuntimeException If the raw object is not in fact a list.
456+
* @param wrap If <code>true</code> and <code>value</code> is not a list, then
457+
* return a newly created list containing only the value.
458+
* @return The input object as a list.
459+
* @throws LinkMLRuntimeException If the raw object is not in fact a list and
460+
* <code>wrap</code> is <code>false</code>.
453461
*/
454462
@SuppressWarnings("unchecked")
455-
protected List<Object> toList(Object value) throws LinkMLRuntimeException {
463+
protected List<Object> toList(Object value, boolean wrap) throws LinkMLRuntimeException {
456464
if ( !(value instanceof List) ) {
457-
throw new LinkMLValueError(LIST_EXPECTED);
465+
if ( wrap ) {
466+
List<Object> l = new ArrayList<>();
467+
l.add(value);
468+
return l;
469+
} else {
470+
throw new LinkMLValueError(LIST_EXPECTED);
471+
}
472+
} else {
473+
return (List<Object>) value;
458474
}
459-
return (List<Object>) value;
460475
}
461476

462477
/**

core/src/test/java/org/incenp/linkml/core/ObjectConverterTest.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,15 @@ void testNormalisingLists() throws IOException {
216216
Assertions.assertEquals("value3", msd.getValues().get(0));
217217
}
218218

219+
@Test
220+
void testScalarAsSingleItemList() throws IOException, LinkMLRuntimeException {
221+
String text = "multiple: sic2\n";
222+
ContainerOfReferences cor = parseString(text, ContainerOfReferences.class);
223+
ctx.finalizeAssignments();
224+
225+
Assertions.assertEquals("sic2", cor.getMultiple().get(0).getId());
226+
}
227+
219228
@Test
220229
void testConvertingContainerOfReferences() throws IOException {
221230
// First read the inlined objects
@@ -454,6 +463,20 @@ void testMultivaluedTypeDesignator() throws IOException, LinkMLRuntimeException
454463
Assertions.assertEquals("DerivedMultiSelfDesignatedClass", types.get(1));
455464
}
456465

466+
@Test
467+
void testSingleMultivaluedTypeDesignator() throws IOException {
468+
// Slot is multivalued but is here provided with a scalar value
469+
String text = "foo: A string\nbar: Another string\ntype: DerivedMultiSelfDesignatedClass\n";
470+
BaseMultiSelfDesignatedClass bmsdc = parseString(text, BaseMultiSelfDesignatedClass.class);
471+
472+
Assertions.assertInstanceOf(DerivedMultiSelfDesignatedClass.class, bmsdc);
473+
DerivedMultiSelfDesignatedClass derived = (DerivedMultiSelfDesignatedClass) bmsdc;
474+
Assertions.assertEquals("A string", derived.getFoo());
475+
Assertions.assertEquals("Another string", derived.getBar());
476+
477+
roundtrip(derived);
478+
}
479+
457480
@Test
458481
void testReferenceToIRIIdentifiers() throws IOException, LinkMLRuntimeException {
459482
ctx.addPrefix("PFX", "https://example.org/");

0 commit comments

Comments
 (0)