Render items for collection-like properties when deriving $jsonSchema.

Closes #3766
Original pull request: #3837.
This commit is contained in:
Christoph Strobl
2021-09-24 12:51:11 +02:00
committed by Mark Paluch
parent c0a4bdb548
commit bbe8410979
2 changed files with 61 additions and 6 deletions

View File

@@ -27,11 +27,11 @@ import org.bson.Document;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.Encrypted;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.mongodb.core.schema.IdentifiableJsonSchemaProperty.ArrayJsonSchemaProperty;
import org.springframework.data.mongodb.core.schema.IdentifiableJsonSchemaProperty.EncryptedJsonSchemaProperty;
import org.springframework.data.mongodb.core.schema.IdentifiableJsonSchemaProperty.ObjectJsonSchemaProperty;
import org.springframework.data.mongodb.core.schema.JsonSchemaObject;
@@ -160,15 +160,15 @@ class MappingMongoJsonSchemaCreator implements MongoJsonSchemaCreator {
Class<?> rawTargetType = computeTargetType(property); // target type before conversion
Class<?> targetType = converter.getTypeMapper().getWriteTargetTypeFor(rawTargetType); // conversion target type
if (property.isEntity() && ObjectUtils.nullSafeEquals(rawTargetType, targetType)) {
if (!isCollection(property) && property.isEntity() && ObjectUtils.nullSafeEquals(rawTargetType, targetType)) {
return createObjectSchemaPropertyForEntity(path, property, required);
}
String fieldName = computePropertyFieldName(property);
JsonSchemaProperty schemaProperty;
if (property.isCollectionLike()) {
schemaProperty = createSchemaProperty(fieldName, targetType, required);
if (isCollection(property)) {
schemaProperty = createSchemaPropertyForCollection(fieldName, property, required);
} else if (property.isMap()) {
schemaProperty = createSchemaProperty(fieldName, Type.objectType(), required);
} else if (ClassUtils.isAssignable(Enum.class, targetType)) {
@@ -180,6 +180,48 @@ class MappingMongoJsonSchemaCreator implements MongoJsonSchemaCreator {
return applyEncryptionDataIfNecessary(property, schemaProperty);
}
private JsonSchemaProperty createSchemaPropertyForCollection(String fieldName, MongoPersistentProperty property,
boolean required) {
ArrayJsonSchemaProperty schemaProperty = JsonSchemaProperty.array(fieldName);
if (property.getActualType() != Object.class) {
MongoPersistentEntity<?> persistentEntity = mappingContext
.getPersistentEntity(property.getTypeInformation().getComponentType());
if (persistentEntity == null) {
if (ClassUtils.isAssignable(Enum.class, property.getActualType())) {
List<Object> possibleValues = new ArrayList<>();
for (Object enumValue : EnumSet.allOf((Class) property.getActualType())) {
possibleValues.add(converter.convertToMongoType(enumValue));
}
Class targetType = possibleValues.isEmpty() ? property.getActualType()
: possibleValues.iterator().next().getClass();
schemaProperty = schemaProperty
.items(Collections.singleton(JsonSchemaObject.of(targetType).possibleValues(possibleValues)));
} else {
schemaProperty = schemaProperty.items(Collections.singleton(JsonSchemaObject.of(property.getActualType())));
}
} else {
List<JsonSchemaProperty> nestedProperties = computePropertiesForEntity(Collections.emptyList(),
persistentEntity);
if (!nestedProperties.isEmpty()) {
schemaProperty = schemaProperty.items(Collections
.singleton(JsonSchemaObject.object().properties(nestedProperties.toArray(new JsonSchemaProperty[0]))));
}
}
}
return createPotentiallyRequiredSchemaProperty(schemaProperty, required);
}
@Nullable
private JsonSchemaProperty applyEncryptionDataIfNecessary(MongoPersistentProperty property,
JsonSchemaProperty schemaProperty) {
@@ -197,7 +239,6 @@ class MappingMongoJsonSchemaCreator implements MongoJsonSchemaCreator {
enc = enc.keys(property.getEncryptionKeyIds());
}
return enc;
}
private JsonSchemaProperty createObjectSchemaPropertyForEntity(List<MongoPersistentProperty> path,
@@ -268,6 +309,10 @@ class MappingMongoJsonSchemaCreator implements MongoJsonSchemaCreator {
return mongoProperty.getFieldType() != mongoProperty.getActualType() ? Object.class : mongoProperty.getFieldType();
}
private static boolean isCollection(MongoPersistentProperty property) {
return property.isCollectionLike() && !property.getType().equals(byte[].class);
}
static JsonSchemaProperty createPotentiallyRequiredSchemaProperty(JsonSchemaProperty property, boolean required) {
if (!required) {

View File

@@ -185,6 +185,9 @@ public class MappingMongoJsonSchemaCreatorUnitTests {
" 'arrayProperty' : { 'type' : 'array' }," + //
" 'binaryDataProperty' : { 'bsonType' : 'binData' }," + //
" 'collectionProperty' : { 'type' : 'array' }," + //
" 'simpleTypeCollectionProperty' : { 'type' : 'array', 'items' : { 'type' : 'string' } }," + //
" 'complexTypeCollectionProperty' : { 'type' : 'array', 'items' : { 'type' : 'object', 'properties' : { 'field' : { 'type' : 'string'} } } }" + //
" 'enumTypeCollectionProperty' : { 'type' : 'array', 'items' : " + JUST_SOME_ENUM + " }" + //
" 'mapProperty' : { 'type' : 'object' }," + //
" 'objectProperty' : { 'type' : 'object' }," + //
" 'enumProperty' : " + JUST_SOME_ENUM + " }" + //
@@ -203,12 +206,19 @@ public class MappingMongoJsonSchemaCreatorUnitTests {
Date dateProperty;
Object[] arrayProperty;
byte[] binaryDataProperty;
List<String> collectionProperty;
List<Object> collectionProperty;
List<String> simpleTypeCollectionProperty;
List<SomeDomainType> complexTypeCollectionProperty;
List<JustSomeEnum> enumTypeCollectionProperty;
Map<String, String> mapProperty;
Object objectProperty;
JustSomeEnum enumProperty;
}
static class SomeDomainType {
String field;
}
// --> NESTED DOMAIN TYPE
static final String WITH_NESTED_DOMAIN_TYPE = "" + //