internal object GetContentItemModel(Type t, JToken item, JToken modularContent, Dictionary <string, object> processedItems = null, HashSet <RichTextContentElements> currentlyResolvedRichStrings = null) { processedItems = processedItems ?? new Dictionary <string, object>(); currentlyResolvedRichStrings = currentlyResolvedRichStrings ?? new HashSet <RichTextContentElements>(); var richTextPropertiesToBeProcessed = new List <PropertyInfo>(); var system = item["system"].ToObject <ContentItemSystemAttributes>(); if (t == typeof(object)) { // Try to find a specific type t = TypeProvider?.GetType(system.Type); if (t == null) { throw new Exception($"No corresponding CLR type found for the '{system.Type}' content type. Provide a correct implementation of '{nameof(ICodeFirstTypeProvider)}' to the '{nameof(TypeProvider)}' property."); } } object instance = Activator.CreateInstance(t); if (!processedItems.ContainsKey(system.Codename)) { processedItems.Add(system.Codename, instance); } foreach (var property in instance.GetType().GetProperties()) { var propertyType = property.PropertyType; if (property.SetMethod != null) { if (propertyType == typeof(ContentItemSystemAttributes)) { // Handle the system metadata if (system != null) { property.SetValue(instance, system); } } else { object value = null; var propValue = ((JObject)item["elements"]).Properties() ?.FirstOrDefault(p => PropertyMapper.IsMatch(property, p.Name, system?.Type)) ?.FirstOrDefault()["value"]; if (propertyType == typeof(string)) { value = propValue?.ToObject <string>(); var links = ((JObject)propValue?.Parent?.Parent)?.Property("links")?.Value; var modularContentInRichText = ((JObject)propValue?.Parent?.Parent)?.Property("modular_content")?.Value; // Handle rich_text link resolution if (links != null && propValue != null && ContentLinkResolver != null) { value = ContentLinkResolver.ResolveContentLinks((string)value, links); } if (modularContentInRichText != null && propValue != null && _client.InlineContentItemsProcessor != null) { // At this point it's clear it's richtext because it contains modular content richTextPropertiesToBeProcessed.Add(property); } } else if (propertyType == typeof(IEnumerable <MultipleChoiceOption>) || propertyType == typeof(IEnumerable <Asset>) || propertyType == typeof(IEnumerable <TaxonomyTerm>) || propertyType.GetTypeInfo().IsValueType) { // Handle non-hierarchical fields value = propValue?.ToObject(propertyType); } else if (propertyType.GetTypeInfo().IsGenericType && ((propertyType.GetInterfaces().Any(gt => gt.GetTypeInfo().IsGenericType&& gt.GetTypeInfo().GetGenericTypeDefinition() == typeof(ICollection <>)) && propertyType.GetTypeInfo().IsClass) || propertyType.GetGenericTypeDefinition() == typeof(IEnumerable <>))) { // Handle modular content var contentItemCodenames = propValue?.ToObject <IEnumerable <string> >(); var modularContentNode = (JObject)modularContent; var genericArgs = propertyType.GetGenericArguments(); // Create a List<T> based on the generic parameter of the input type (IEnumerable<T> or derived types) Type collectionType = propertyType.GetTypeInfo().IsInterface ? typeof(List <>).MakeGenericType(genericArgs) : propertyType; object contentItems = Activator.CreateInstance(collectionType); if (contentItemCodenames != null && contentItemCodenames.Any()) { foreach (string codename in contentItemCodenames) { var modularContentItemNode = modularContentNode.Properties().FirstOrDefault(p => p.Name == codename)?.First; if (modularContentItemNode != null) { object contentItem = null; if (processedItems.ContainsKey(codename)) { // Avoid infinite recursion by re-using already processed content items contentItem = processedItems[codename]; } else { if (genericArgs.First() == typeof(ContentItem)) { contentItem = new ContentItem(modularContentItemNode, modularContentNode, _client); } else { contentItem = GetContentItemModel(genericArgs.First(), modularContentItemNode, modularContentNode, processedItems); } if (!processedItems.ContainsKey(codename)) { processedItems.Add(codename, contentItem); } } // It certain that the instance is of the ICollection<> type at this point, we can call "Add" contentItems.GetType().GetMethod("Add").Invoke(contentItems, new[] { contentItem }); } } } value = contentItems; } if (value != null) { property.SetValue(instance, value); } } } } // Richtext elements need to be processed last, so in case of circular dependency, content items resolved by // resolvers would have all elements already processed foreach (var property in richTextPropertiesToBeProcessed) { var value = property.GetValue(instance).ToString(); var propValue = ((JObject)item["elements"]).Properties() ?.FirstOrDefault(p => PropertyMapper.IsMatch(property, p.Name, system?.Type)) ?.FirstOrDefault()["value"]; var modularContentInRichText = ((JObject)propValue?.Parent?.Parent)?.Property("modular_content")?.Value; var currentlyProcessedString = new RichTextContentElements() { ContentItemCodeName = system.Codename, RichTextElementCodeName = property.Name }; if (currentlyResolvedRichStrings.Contains(currentlyProcessedString)) { // If this element is already being processed it's necessary to to use it as is (with removed inline content items) // otherwise resolving would be stuck in an infinite loop value = RemoveInlineContentItems(value); } else { currentlyResolvedRichStrings.Add(currentlyProcessedString); value = ProcessInlineContentItems(modularContent, processedItems, value, modularContentInRichText, currentlyResolvedRichStrings); currentlyResolvedRichStrings.Remove(currentlyProcessedString); } if (value != null) { property.SetValue(instance, value); } } return(instance); }