protected virtual object MapField(object fieldValues, Type modelPropertyType, SemanticSchemaField semanticSchemaField, MappingData mappingData)
        {
            Type sourceType = fieldValues.GetType();
            bool isArray    = sourceType.IsArray;

            if (isArray)
            {
                sourceType = sourceType.GetElementType();
            }

            bool isListProperty = modelPropertyType.IsGenericList();
            Type targetType     = modelPropertyType.GetUnderlyingGenericListType() ?? modelPropertyType;

            // Convert.ChangeType cannot convert non-nullable types to nullable types, so don't try that.
            Type bareTargetType = modelPropertyType.GetUnderlyingNullableType() ?? targetType;

            IList mappedValues = targetType.CreateGenericList();

            if (typeof(EntityModel).IsAssignableFrom(targetType) && sourceType != typeof(EntityModelData) &&
                (sourceType == typeof(string) && string.IsNullOrEmpty((string)fieldValues)))
            {
                return(isListProperty ? mappedValues : null);
            }

            switch (sourceType.Name)
            {
            case "String":
                if (isArray)
                {
                    foreach (string fieldValue in (string[])fieldValues)
                    {
                        mappedValues.Add(MapString(fieldValue, bareTargetType));
                    }
                }
                else
                {
                    mappedValues.Add(MapString((string)fieldValues, bareTargetType));
                }
                break;

            case "DateTime":
            case "Single":
            case "Double":
                if (isArray)
                {
                    foreach (object fieldValue in (Array)fieldValues)
                    {
                        mappedValues.Add(Convert.ChangeType(fieldValue, bareTargetType));
                    }
                }
                else
                {
                    mappedValues.Add(Convert.ChangeType(fieldValues, bareTargetType));
                }
                break;

            case "RichTextData":
                if (isArray)
                {
                    foreach (RichTextData fieldValue in (RichTextData[])fieldValues)
                    {
                        mappedValues.Add(MapRichText(fieldValue, targetType, mappingData.Localization));
                    }
                }
                else
                {
                    mappedValues.Add(MapRichText((RichTextData)fieldValues, targetType, mappingData.Localization));
                }
                break;

            case "ContentModelData":
                string fieldXPath = semanticSchemaField.GetXPath(mappingData.ContextXPath);
                if (isArray)
                {
                    int index = 1;
                    foreach (ContentModelData embeddedFields in (ContentModelData[])fieldValues)
                    {
                        string indexedFieldXPath = $"{fieldXPath}[{index++}]";
                        mappedValues.Add(MapEmbeddedFields(embeddedFields, targetType, semanticSchemaField, indexedFieldXPath, mappingData));
                    }
                }
                else
                {
                    string indexedFieldXPath = $"{fieldXPath}[1]";
                    mappedValues.Add(MapEmbeddedFields((ContentModelData)fieldValues, targetType, semanticSchemaField, indexedFieldXPath, mappingData));
                }
                break;

            case "EntityModelData":
                if (isArray)
                {
                    foreach (EntityModelData entityModelData in (EntityModelData[])fieldValues)
                    {
                        mappedValues.Add(MapComponentLink(entityModelData, targetType, mappingData.Localization));
                    }
                }
                else
                {
                    mappedValues.Add(MapComponentLink((EntityModelData)fieldValues, targetType, mappingData.Localization));
                }
                break;

            case "KeywordModelData":
                if (isArray)
                {
                    foreach (KeywordModelData keywordModelData in (KeywordModelData[])fieldValues)
                    {
                        mappedValues.Add(MapKeyword(keywordModelData, targetType, mappingData.Localization));
                    }
                }
                else
                {
                    mappedValues.Add(MapKeyword((KeywordModelData)fieldValues, targetType, mappingData.Localization));
                }
                break;


            default:
                throw new DxaException($"Unexpected field type: '{sourceType.Name}'.");
            }

            return(isListProperty ? mappedValues : ((mappedValues.Count == 0) ? null : mappedValues[0]));
        }
        protected virtual void MapSemanticProperties(ViewModel viewModel, MappingData mappingData)
        {
            Type modelType = viewModel.GetType();
            IDictionary <string, List <SemanticProperty> > propertySemanticsMap = ModelTypeRegistry.GetPropertySemantics(modelType);
            IDictionary <string, string> xpmPropertyMetadata = new Dictionary <string, string>();
            Validation validation = mappingData.PropertyValidation;

            foreach (KeyValuePair <string, List <SemanticProperty> > propertySemantics in propertySemanticsMap)
            {
                PropertyInfo            modelProperty      = modelType.GetProperty(propertySemantics.Key);
                List <SemanticProperty> semanticProperties = propertySemantics.Value;

                bool   isFieldMapped = false;
                string fieldXPath    = null;
                foreach (SemanticProperty semanticProperty in semanticProperties)
                {
                    if (semanticProperty.PropertyName == SemanticProperty.AllFields)
                    {
                        modelProperty.SetValue(viewModel, GetAllFieldsAsDictionary(mappingData));
                        isFieldMapped = true;
                        break;
                    }


                    if ((semanticProperty.PropertyName == SemanticProperty.Self) && validation.MainSchema.HasSemanticType(semanticProperty.SemanticType))
                    {
                        try
                        {
                            object mappedSelf = MapComponentLink((EntityModelData)mappingData.SourceViewModel, modelProperty.PropertyType, mappingData.Localization);
                            modelProperty.SetValue(viewModel, mappedSelf);
                            isFieldMapped = true;
                            break;
                        }
                        catch (Exception ex)
                        {
                            Log.Debug($"Self mapping failed for {modelType.Name}.{modelProperty.Name}: {ex.Message}");
                            continue;
                        }
                    }

                    FieldSemantics fieldSemantics = new FieldSemantics(
                        semanticProperty.SemanticType.Vocab,
                        semanticProperty.SemanticType.EntityName,
                        semanticProperty.PropertyName,
                        null);
                    SemanticSchemaField semanticSchemaField = (mappingData.EmbeddedSemanticSchemaField == null) ?
                                                              ValidateField(validation, fieldSemantics) :
                                                              mappingData.EmbeddedSemanticSchemaField.FindFieldBySemantics(fieldSemantics);
                    if (semanticSchemaField == null)
                    {
                        // No matching Semantic Schema Field found for this Semantic Property; maybe another one will match.
                        continue;
                    }

                    // Matching Semantic Schema Field found
                    fieldXPath = IsFieldFromMainSchema(validation, fieldSemantics) ? semanticSchemaField.GetXPath(mappingData.ContextXPath) : null;

                    ContentModelData fields     = semanticSchemaField.IsMetadata ? mappingData.MetadataFields : mappingData.Fields;
                    object           fieldValue = FindFieldValue(semanticSchemaField, fields, mappingData.EmbedLevel);
                    if (fieldValue == null)
                    {
                        // No field value found; maybe we will find a value for another Semantic Property.
                        continue;
                    }

                    try
                    {
                        object mappedPropertyValue = MapField(fieldValue, modelProperty.PropertyType, semanticSchemaField, mappingData);
                        modelProperty.SetValue(viewModel, mappedPropertyValue);
                    }
                    catch (Exception ex)
                    {
                        throw new DxaException(
                                  $"Unable to map field '{semanticSchemaField.Name}' to property {modelType.Name}.{modelProperty.Name} of type '{modelProperty.PropertyType.FullName}'.",
                                  ex);
                    }
                    isFieldMapped = true;
                    break;
                }

                if (fieldXPath != null)
                {
                    xpmPropertyMetadata.Add(modelProperty.Name, fieldXPath);
                }
                else if (!isFieldMapped && Log.IsDebugEnabled)
                {
                    string formattedSemanticProperties = string.Join(", ", semanticProperties.Select(sp => sp.ToString()));
                    Log.Debug(
                        $"Property {modelType.Name}.{modelProperty.Name} cannot be mapped to a CM field of {validation.MainSchema}. Semantic properties: {formattedSemanticProperties}.");
                }
            }

            EntityModel entityModel = viewModel as EntityModel;

            if ((entityModel != null) && mappingData.Localization.IsXpmEnabled)
            {
                entityModel.XpmPropertyMetadata = xpmPropertyMetadata;
            }
        }