43 internal static ConverterSettings ConverterSettings {
get;
set; }
44 internal static Dictionary<Type, MethodInfo> ToStringCache {
get;
set; } =
new Dictionary<Type, MethodInfo>();
46 public static List<dynamic> ConvertClassToList(dynamic myClass)
48 Type myClassType = myClass.GetType();
49 PropertyInfo[] properties = myClassType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
50 FieldInfo[] fields = myClassType.GetFields(BindingFlags.Public | BindingFlags.Instance);
51 List<dynamic> list =
new List<dynamic>();
57 if (IsDictionaryType(myClassType))
60 foreach (KeyValuePair<dynamic, dynamic> item
in myClass)
62 Type elementType = item.GetType();
64 if (
true == IsSimpleType(elementType))
68 myList = ConvertClassToList(item.Value);
75 else if (IsEnumerableType(myClassType))
78 foreach (var item
in myClass)
80 Type elementType = item.GetType();
82 if (
true == IsSimpleType(elementType))
86 myList = ConvertClassToList(item);
93 else if (
true == myClassType.IsArray)
95 var arrayList = myClass as Array;
96 int len = arrayList.Length;
98 for (
int idx = 0; idx < len; idx++)
100 var elementValue = arrayList.GetValue(idx);
101 var elementType = elementValue.GetType();
103 if (
true == IsSimpleType(elementType))
104 list.Add(elementValue);
107 myList = ConvertClassToList(elementValue);
114 if (
null != properties)
115 foreach (PropertyInfo property
in properties)
118 if (property.GetIndexParameters().Length > 0 ||
119 property.DeclaringType !=
null && property.DeclaringType.Namespace.StartsWith(
"System.Collections"))
124 Type propertyType =
property.PropertyType;
126 if (property.CanRead)
128 dynamic value =
null;
130 if (
true == propertyType.IsArray)
132 var arrayList =
property.GetValue(myClass) as Array;
133 int len = arrayList.Length;
135 for (
int idx = 0; idx < len; idx++)
137 var elementValue = arrayList.GetValue(idx);
138 var elementType = elementValue.GetType();
140 if (
true == IsSimpleType(elementType))
141 list.Add(elementValue);
142 else if (
true == IsEnumerableType(elementType))
144 IEnumerable enumeration = (IEnumerable)elementValue;
145 list.Add(enumeration);
147 else if (
true == IsDictionaryType(elementType))
149 myList = ConvertClassToList(elementValue);
158 value =
property.GetValue(myClass);
162 GPAL.
PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Unable to getvalue of property [{property.Name}][{propertyType}]. Continuing and hoping for the best.", myClass, GPALObjectType.Other, ex);
167 if (IsBigIntegerType(propertyType) || IsVersionType(propertyType))
169 list.Add(value.ToString());
171 else if (IsSimpleType(propertyType))
173 list.Add(ConvertValue(value, propertyType));
175 else if (IsDictionaryType(propertyType))
177 list.Add(ConvertClassToList(value));
179 else if (IsEnumerableType(propertyType))
182 foreach (var item
in value)
184 var elementType = item.GetType();
186 if (
true == IsSimpleType(elementType))
188 else if (
true == IsComplexType(elementType))
190 string sign = item.Imaginary >= 0 ?
"+" :
"";
191 list.Add($
"{item.Real.ToString(CultureInfo.InvariantCulture)}{sign}{item.Imaginary.ToString(CultureInfo.InvariantCulture)}i");
195 myList = ConvertClassToList(item);
201 else if (
true == IsComplexType(propertyType))
203 string sign = value.Imaginary >= 0 ?
"+" :
"";
204 list.Add($
"{value.Real.ToString(CultureInfo.InvariantCulture)}{sign}{value.Imaginary.ToString(CultureInfo.InvariantCulture)}i");
208 list.Add(ConvertClassToList(value));
216 foreach (FieldInfo field
in fields)
219 object value = field.GetValue(myClass);
222 if (IsSimpleType(field.FieldType))
226 else if (IsDictionaryType(field.FieldType))
228 list.Add(ConvertClassToList(value));
230 else if (IsEnumerableType(field.FieldType))
232 foreach (
object item
in (IEnumerable<dynamic>)value)
234 myList = ConvertClassToList(item);
241 list.Add(ConvertClassToList(value));
253 public new bool Equals(
object x,
object y) => ReferenceEquals(x, y);
254 public int GetHashCode(
object obj) => System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(obj);
256 public static Dictionary<object, dynamic> ConvertClassToDictionary(
259 HashSet<object> visited =
null)
262 bool isRootCall = visited ==
null;
268 Dictionary<object, dynamic> dict =
new Dictionary<object, dynamic>();
270 if (
false == IsCustomStructType(myClass?.GetType()) &&
false == IsTupleType(myClass?.GetType()) && myClass ==
null)
276 if (myClass.GetType().IsClass || myClass.GetType().IsInterface)
278 if (visited.TryGetValue(myClass, out dynamic fuggetAboutIt))
280 string referenceName =
string.Empty;
284 referenceName = myClass.Value;
290 referenceName = myClass.Name;
294 referenceName = myClass.GetType().Name;
298 return new Dictionary<object, dynamic>
300 { name, $
"Recursion to [{referenceName}]" }
306 visited.Add(myClass);
309 bool addedToVisited =
false == IsCustomStructType(myClass?.GetType()) &&
false == IsTupleType(myClass?.GetType()) && myClass !=
null && (myClass?.GetType().IsClass || myClass?.GetType().IsInterface);
313 Type myClassType = myClass?.GetType();
314 var properties = (from p in myClassType?.GetProperties(BindingFlags.Public | BindingFlags.Instance)
315 group p by p.Name into g
316 select g.OrderByDescending(t => t.DeclaringType == myClassType).First());
318 FieldInfo[] fields = myClassType?.GetFields(BindingFlags.Public | BindingFlags.Instance);
319 List<dynamic> list =
new List<dynamic>();
323 if (IsClassType(myClassType) &&
324 !myClassType.IsAbstract &&
325 !myClassType.IsInterface &&
326 !myClassType.IsArray &&
327 !IsSimpleType(myClassType) &&
328 !IsEnumerableType(myClassType) &&
329 !IsDictionaryType(myClassType) &&
330 !myClassType.IsDefined(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute),
false))
332 goto ProcessPropertiesAndFields;
336 if (IsDictionaryType(myClassType))
344 void AddEntry(dynamic entryKey, dynamic entryValue)
346 object outputKey = IsSimpleType(entryKey?.GetType()) ? (object)ConvertValue(entryKey, entryKey?.GetType()) : entryKey;
350 object outputValue = IsSimpleType(entryValue?.GetType())
351 ? (object)ConvertValue(entryValue, entryValue?.GetType())
352 : ConvertClassToDictionary(entryValue, entryKey?.ToString(), visited);
353 dict.Add(outputKey, outputValue);
358 var dictionary = (IDictionary)myClass;
359 foreach (var key
in dictionary.Keys)
361 AddEntry(key, dictionary[key]);
367 foreach (var item
in (IEnumerable)myClass)
369 var itemType = item.GetType();
370 var keyProp = itemType.GetProperty(
"Key");
371 var valueProp = itemType.GetProperty(
"Value");
372 if (keyProp !=
null && valueProp !=
null)
374 AddEntry(keyProp.GetValue(item), valueProp.GetValue(item));
378 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $
"Unable to convert class [{itemType}] Key [{keyProp?.Name}]/Value [{valueProp?.Name}] to dictionary.", myClass, GPALObjectType.Other);
384 else if (IsEnumerableType(myClassType))
388 foreach (dynamic item
in (IEnumerable)myClass)
390 Type elementType = item?.GetType();
391 if (elementType ==
null)
continue;
393 string keyName = $
"GPALKEY{idx:D4}";
395 if (IsSimpleType(elementType))
397 dict.Add(keyName, ConvertValue(item, elementType));
399 else if (IsComplexType(elementType))
401 string sign = item.Imaginary >= 0 ?
"+" :
"";
402 dict.Add(keyName, $
"{item.Real.ToString(CultureInfo.InvariantCulture)}{sign}{item.Imaginary.ToString(CultureInfo.InvariantCulture)}i");
404 else if (IsClassType(elementType) && !IsEnumerableType(elementType) && !IsDictionaryType(elementType) && !elementType.IsArray)
406 myDict = ConvertClassToDictionary(item,
"", visited);
407 dict.Add(keyName, myDict);
409 else if (IsDictionaryType(elementType) || IsEnumerableType(elementType) || elementType.IsArray)
411 myDict = ConvertClassToDictionary(item,
"", visited);
412 dict.Add(keyName, myDict);
416 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Unsupported element type [{elementType}] in enumerable [{myClassType}]", item, GPALObjectType.Other);
423 ProcessPropertiesAndFields:
424 if (properties !=
null)
426 foreach (PropertyInfo property
in properties)
428 if (property.GetIndexParameters().Length > 0)
433 if (property.DeclaringType == myClassType &&
434 property.DeclaringType.Namespace !=
null &&
435 property.DeclaringType.Namespace.StartsWith(
"System.Collections"))
440 Type propertyType =
property.PropertyType;
441 list =
new List<dynamic>();
443 if (property.CanRead)
445 dynamic value =
null;
448 value =
property.GetValue(myClass);
452 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Unable to getvalue of property [{property.Name}][{propertyType}]. Continuing and hoping for the best.", myClass, GPALObjectType.Other, ex);
455 if ((
object)value ==
null)
458 if (
false == IsTupleType(value?.GetType()))
460 if (propertyType.IsArray)
462 var arrayList = (Array)property.GetValue(myClass);
463 if (
null != arrayList)
464 for (
int idx = 0; idx < arrayList.Length; idx++)
466 var elementValue = arrayList.GetValue(idx);
467 var elementType = elementValue?.GetType() ?? typeof(
object);
470 if (IsSimpleType(elementType))
472 list.Add(ConvertValue(elementValue, elementType));
474 else if (IsEnumerableType(elementType))
476 list.Add((IEnumerable)elementValue);
478 else if (IsDictionaryType(elementType))
480 list.Add(ConvertClassToDictionary(elementValue, $
"{property.Name}{idx}", visited));
482 else if (IsCustomStructType(elementType) || IsClassType(elementType) || IsTupleType(elementType))
484 list.Add(ConvertClassToDictionary(elementValue, $
"{property.Name}{idx}", visited));
488 list.Add(ValueToString(arrayList));
491 dict.Add(property.Name, list);
493 else if (IsSimpleType(propertyType))
495 dict.Add(property.Name, ConvertValue(value, propertyType));
497 else if (IsDictionaryType(propertyType))
499 dict.Add(property.Name, ConvertClassToDictionary(value, $
"{propertyType}", visited));
501 else if (IsEnumerableType(propertyType))
505 foreach (var item
in value)
507 var elementType = item?.GetType() ?? typeof(
object);
508 if (
true == IsCustomStructType(item?.GetType()))
510 list.Add(ConvertClassToDictionary(item, $
"{property.Name}{idx}", visited));
516 else if (IsSimpleType(elementType))
518 list.Add(ConvertValue(item, elementType));
520 else if (IsComplexType(elementType))
522 string sign = item.Imaginary >= 0 ?
"+" :
"";
523 list.Add($
"{item.Real.ToString(CultureInfo.InvariantCulture)}{sign}{item.Imaginary.ToString(CultureInfo.InvariantCulture)}i");
525 else if (
true == IsGuidType(elementType))
527 list.Add(Guid.TryParse(item.ToString(), out Guid outGuid) ? outGuid : item);
529 else if (IsEnumerableType(elementType))
531 list.Add((IEnumerable)item);
535 list.Add(ConvertClassToDictionary(item, $
"{property.Name}{idx}", visited));
540 list.Add(ValueToString(value));
543 dict.Add(property.Name, list);
545 else if (IsComplexType(propertyType))
547 string sign = value.Imaginary >= 0 ?
"+" :
"";
548 dict.Add(property.Name, $
"{value.Real.ToString(CultureInfo.InvariantCulture)}{sign}{value.Imaginary.ToString(CultureInfo.InvariantCulture)}i");
550 else if (IsClassType(propertyType) || IsCustomStructType(value?.GetType()))
552 dict.Add(property.Name, ConvertClassToDictionary(value, property.Name, visited));
556 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Unknown property type [{myClass.GetType()}][{propertyType}] detected. Adding [{value}] to dict.", myClass, GPALObjectType.Other);
557 dict.Add(property.Name, value);
562 dict.Add(property.Name, ConvertClassToDictionary(value, property.Name, visited));
570 foreach (FieldInfo field
in fields)
572 object value = field.GetValue(myClass);
573 if (
true == IsCustomStructType(value?.GetType()) || value !=
null)
575 if (IsSimpleType(field.FieldType))
577 dict.Add(field.Name, ConvertValue(value, field.FieldType));
579 else if (IsDictionaryType(field.FieldType))
581 dict.Add(field.Name, ConvertClassToDictionary(value, field.Name, visited));
583 else if (IsEnumerableType(field.FieldType))
586 list =
new List<dynamic>();
587 foreach (
object item
in (IEnumerable)value)
589 Type itemType = item?.GetType() ?? typeof(
object);
590 list.Add(IsSimpleType(itemType) ? ConvertValue(item, itemType) : ConvertClassToDictionary(item, $
"{itemType}{idx}", visited));
593 dict.Add(field.Name, list);
597 dict.Add(field.Name, ConvertClassToDictionary(value, field.Name, visited));
609 visited.Remove(myClass);
612 public static dynamic ConvertToClass(dynamic dictList, Type targetType, dynamic parentDictionary, List<string> columnNames)
615 Type createType = GetConcreteType(targetType);
616 dynamic myDictList = dictList;
617 int dictListLength = 0;
619 dictListLength = ObjectCopier.GetItemCount(
null, dictList);
621 if ((IsEnumerableObject(myDictList) || IsArray(myDictList)) && !IsDictionaryType(myDictList.GetType()) && 1 == dictListLength)
623 dynamic dictList2 = dictList[0];
624 if (
true == IsDictionaryType(dictList2.GetType()))
626 myDictList = dictList2;
630 dictListLength = ObjectCopier.GetItemCount(
null, myDictList);
634 if (
true == IsDictionaryType(myDictList.GetType()))
636 int nestedDictCount = 0;
638 foreach (var item
in myDictList)
640 if (
true == IsDictionaryType(item?.Value?.GetType()))
654 bool singleRowLooksIndexed =
false;
655 if (1 == dictListLength && 1 == nestedDictCount)
657 singleRowLooksIndexed =
true;
658 foreach (var soloRowItem
in myDictList)
660 string soloRowKeyStr = soloRowItem.Key?.ToString();
661 if (
false ==
int.TryParse(soloRowKeyStr, out
int soloRowKeyDummy))
663 singleRowLooksIndexed =
false;
672 if (nestedDictCount == dictListLength && (1 < dictListLength || singleRowLooksIndexed)
673 && (
true == IsDictionaryType(createType) ||
true == IsEnumerableType(createType)))
675 Type listType = typeof(List<>).MakeGenericType(createType);
676 IList result = (IList)InstantiateOne(listType);
680 if (
true == IsDictionaryType(createType))
682 IDictionary tmpDict = (IDictionary)InstantiateOne(createType);
683 Type dictKeyType = createType.GetGenericArguments()[0];
684 foreach (var item
in myDictList)
688 dynamic element = ConvertToClass(item.Value, ConverterSettings.OutputClassElementType, parentDictionary, columnNames);
689 object resolvedKey = IsSimpleType(dictKeyType)
690 ? ResolveSimpleDictionaryKey(item.Key, dictKeyType)
691 : (item.Key is IDictionary keyDict ? CreateObjectFromDictionary(keyDict, dictKeyType, parentDictionary, null) : item.Key);
692 tmpDict[resolvedKey] = element;
696 tmpDict = ConvertToClass(item.Value, createType, parentDictionary, columnNames);
701 else if (
true == IsEnumerableType(createType))
703 object container = InstantiateOne(createType, dictListLength);
713 Type rowElementType = createType.IsArray
714 ? createType.GetElementType()
715 : (createType.IsGenericType && createType.GetGenericArguments().Length > 0 ? createType.GetGenericArguments()[0] : ConverterSettings.OutputClassElementType);
717 foreach (var item
in myDictList)
719 object element = ConvertToClass(item.Value, rowElementType, parentDictionary, columnNames);
723 AddToList(container, element, index);
730 result.Add(container);
733 if (1 < result.Count)
742 dynamic unwrapped = myDictList;
743 if (myDictList.Count == 1)
745 foreach (var kvp
in myDictList)
747 if (kvp.Value is IDictionary)
749 string key = kvp.Key?.ToString() ??
string.Empty;
750 bool isRealProperty =
751 createType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
752 .Any(p =>
string.Equals(p.Name, key, StringComparison.OrdinalIgnoreCase))
753 || createType.GetFields(BindingFlags.Public | BindingFlags.Instance)
754 .Any(f =>
string.Equals(f.Name, key, StringComparison.OrdinalIgnoreCase));
756 unwrapped = kvp.Value;
761 return CreateObjectFromDictionary(unwrapped, createType, parentDictionary, columnNames);
764 else if (IsEnumerableObject(myDictList) || IsArray(myDictList))
766 Type listType = typeof(List<>).MakeGenericType(createType);
767 IList result = (IList)InstantiateOne(listType);
768 dynamic createdType = InstantiateOne(createType);
772 foreach (var item
in myDictList)
774 object element = CreateObjectFromDictionary(item, ConverterSettings.OutputClassElementType, parentDictionary, columnNames);
775 AddToList(createdType, element, count++);
778 result.Add(createdType);
780 if (1 < result.Count)
788 return CreateObjectFromDictionary(myDictList, createType, parentDictionary, columnNames);
792 static dynamic lastParentDictionary =
null;
793 static List<string> lastErrorMessage =
new List<string>();
794 static bool supressedMessage =
false;
795 [System.ThreadStatic]
796 private static HashSet<object> _dictVisited;
805 internal static object CreateObjectFromDictionary(dynamic dictionary, Type targetType, dynamic parentDictionary, List<string> columnNames)
809 bool isRoot = _dictVisited ==
null;
811 bool addedToVisited =
false;
812 if (dictionary !=
null && !(targetType?.IsValueType ==
true))
814 object dictRef = (object)dictionary;
815 if (!_dictVisited.Add(dictRef))
816 return InstantiateOne(targetType);
817 addedToVisited =
true;
820 dynamic objOrList =
null;
821 string kvpName =
string.Empty;
822 string kvpPropertyOrFieldName =
string.Empty;
823 dynamic kvpValue =
null;
824 PropertyInfo kvpProperty =
null;
825 FieldInfo kvpField =
null;
826 Type kvpPropertyType =
null;
827 Type priorKvpPropertyType =
null;
828 Type kvpFieldType =
null;
829 object itemobj =
null;
830 int noPropertyOrField = 0;
832 bool isEnumerable = IsEnumerableType(targetType);
836 if (dictionary is IList cofdSingleList && cofdSingleList.Count == 1 && cofdSingleList[0] is IDictionary cofdInnerDict
837 && !isEnumerable && !targetType.IsArray && !IsDictionaryType(targetType))
839 dictionary = cofdInnerDict;
843 bool HasParentChanged(dynamic lastParent, dynamic currentParent)
847 if (currentParent ==
null)
851 if (lastParent ==
null)
855 if (lastParent.GetType() != currentParent.GetType())
860 return !ReferenceEquals(lastParent, currentParent);
863 if (
true == HasParentChanged(lastParentDictionary, parentDictionary))
865 lastParentDictionary = parentDictionary;
866 lastErrorMessage.Clear();
867 supressedMessage =
false;
870 if (
true == isEnumerable || targetType.IsArray)
871 count = ObjectCopier.GetItemCount(
null, dictionary);
873 if (IsComplexType(targetType))
874 return ComplexFromDict(dictionary);
879 if (targetType == typeof(
object) && dictionary is IDictionary)
884 objOrList = ConverterHelper.InstantiateOne(targetType, count);
888 Type listType = typeof(List<>).MakeGenericType(targetType);
889 objOrList = ActivatorHelper.CreateInstance(listType);
892 if (IsDictionaryType(targetType))
894 if (dictionary is IDictionary dictInput)
896 Type keyType = targetType.GetGenericArguments()[0];
897 Type valueType = targetType.GetGenericArguments()[1];
898 IDictionary dict = (IDictionary)objOrList;
899 foreach (dynamic kvp
in dictInput)
901 object key = IsSimpleType(keyType)
902 ? ResolveSimpleDictionaryKey(kvp.Key, keyType)
903 : (kvp.Key is IDictionary keyDict ? CreateObjectFromDictionary(keyDict, keyType, dictInput, null) : kvp.Key);
904 object value = kvp.Value?.ToString().Trim(
'"') ??
string.Empty;
909 if (kvp.Value !=
null && valueType.IsAssignableFrom(kvp.Value.GetType()))
914 else if (kvp.Value is IDictionary && IsClassType(valueType) && valueType != typeof(
string))
916 value = ConvertToClass(kvp.Value, valueType, dictInput, columnNames);
919 else if (IsDictionaryType(valueType) && kvp.Value is IDictionary)
921 value = CreateObjectFromDictionary(kvp.Value, valueType, dictInput, columnNames);
924 else if (IsSimpleType(valueType) || IsNullableType(valueType))
926 value = ConvertValue(kvp.Value, valueType);
929 else if (IsEnumerableType(valueType) && valueType != typeof(
string))
931 Type elemType = valueType.GetGenericArguments().Length > 0
932 ? valueType.GetGenericArguments()[0]
933 : valueType.GetElementType();
934 if (elemType !=
null)
936 dynamic resultList = InstantiateOne(valueType);
938 IEnumerable innerEnum = kvp.Value is IEnumerable enumer && !(kvp.Value is string)
940 : new object[] { kvp.Value };
941 foreach (var innerItem
in innerEnum)
943 if (innerItem is IDictionary innerDict && IsClassType(elemType) && elemType != typeof(
string))
944 AddToList(resultList, CreateObjectFromDictionary(innerDict, elemType, dictInput, columnNames), i2++);
945 else if (IsSimpleType(elemType) || IsNullableType(elemType))
946 AddToList(resultList, ConvertValue(innerItem, elemType), i2++);
947 else if (IsBigIntegerType(elemType))
949 if (innerItem is
string biNullStr2 && IsNullableType(elemType) && biNullStr2.Trim().Equals(
"null", StringComparison.OrdinalIgnoreCase))
950 AddToList(resultList,
null, i2++);
953 try { AddToList(resultList, AsBigInteger(innerItem), i2++); }
954 catch { GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Invalid BigInteger format [{innerItem}] for element type [{elemType}]", innerItem, GPALObjectType.Other); }
958 AddToList(resultList, innerItem, i2++);
968 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Failed to add key[{key}]-value[{value}] pair to dictionary [{targetType}]", kvp, GPALObjectType.Other, ex);
975 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Expected IDictionary input for [{targetType}]", dictionary, GPALObjectType.None);
980 else if (
true == isEnumerable)
982 Type elementType = targetType.IsArray ? targetType.GetElementType() : (targetType.GetGenericArguments().Length > 0 ? targetType.GetGenericArguments()[0] : targetType);
983 if (dictionary is IDictionary dictInput)
985 bool isIndexed = dictInput.Keys.Cast<dynamic>().All(key =>
int.TryParse(key?.ToString(), out
int dummy));
988 dynamic keys = dictInput.Keys;
990 foreach (var key
in keys)
994 object value = dictInput[key];
995 if (value is IDictionary classDict && IsClassType(elementType) && elementType != typeof(
string))
997 object classInstance = CreateObjectFromDictionary(classDict, elementType, keys, columnNames);
998 if (classInstance !=
null)
1000 AddToList(objOrList, classInstance, count++);
1004 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"ConvertToClass returned null for [{elementType}] with dict [{classDict}]", classDict, GPALObjectType.Other);
1007 else if (IsSimpleType(elementType) && (value ==
null || IsSimpleType(value.GetType())))
1010 object convertedValue = ConvertValue(value, elementType);
1011 AddToList(objOrList, convertedValue, count++);
1015 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Expected IDictionary for TestClass or simple type, got [{value?.GetType()}] for element type [{elementType}]", key, GPALObjectType.Other);
1018 catch (Exception ex)
1020 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Failed to add value to enumerable [{targetType}]", key, GPALObjectType.Other, ex);
1026 foreach (dynamic kvp
in dictionary)
1031 dynamic value = kvp.Value;
1034 if (elementType.IsValueType && !IsNullableType(elementType))
1036 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Null value not allowed for non-nullable type [{elementType}]", kvp, GPALObjectType.Other);
1040 AddToList(objOrList,
null, count++);
1043 else if (elementType.IsAssignableFrom(value.GetType()))
1045 AddToList(objOrList, value, count++);
1047 else if (IsCustomStructType(elementType))
1050 var converter = TypeDescriptor.GetConverter(elementType);
1051 if (converter !=
null && converter.CanConvertFrom(value.GetType()))
1053 value = converter.ConvertFrom(value);
1054 AddToList(objOrList, value, count++);
1060 AddToList(objOrList, value, count++);
1064 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Unsupported struct type [{value.GetType()}] for element type [{elementType}]", kvp, GPALObjectType.Other);
1068 else if (value is IDictionary nestedDict)
1070 if (IsClassType(elementType) && elementType != typeof(
string))
1072 bool nestedIsIndexed = nestedDict.Keys.Cast<dynamic>().All(k =>
int.TryParse(k?.ToString(), out
int dummy));
1073 if (!nestedIsIndexed)
1075 value = ConvertToClass(nestedDict, elementType, dictionary, columnNames);
1076 AddToList(objOrList, value, count++);
1079 else if (IsDictionaryType(elementType) || IsCustomStructType(elementType) || IsTupleType(elementType))
1081 value = CreateObjectFromDictionary(nestedDict, elementType, dictionary, columnNames);
1082 AddToList(objOrList, value, count++);
1086 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Dictionary value [{value.GetType()}] incompatible with element type [{elementType}]", kvp, GPALObjectType.Other);
1089 else if (IsEnumerableType(elementType) && value is IEnumerable enumValue)
1091 value = CreateObjectFromDictionary(enumValue, elementType, dictionary, columnNames);
1092 AddToList(objOrList, value, count++);
1094 else if (IsSimpleType(elementType) || IsNullableType(elementType))
1096 if (IsSimpleType(value.GetType()) || IsNullableType(value.GetType()))
1098 value = ConvertValue(value, elementType);
1099 AddToList(objOrList, value, count++);
1103 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Value type [{value.GetType()}] incompatible with simple/nullable element type [{elementType}]", kvp, GPALObjectType.Other);
1106 else if (IsComplexType(elementType))
1108 if (value is
string complexStr)
1110 value = ParseComplex(complexStr);
1111 AddToList(objOrList, value, count++);
1113 else if (value is Complex complexValue)
1115 AddToList(objOrList, complexValue, count++);
1119 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Value type [{value.GetType()}] incompatible with complex/nullable element type [{elementType}]", kvp, GPALObjectType.Other);
1122 else if (IsBigIntegerType(elementType))
1124 if (value is
string biNullStr && IsNullableType(elementType) && biNullStr.Trim().Equals(
"null", StringComparison.OrdinalIgnoreCase))
1125 AddToList(objOrList,
null, count++);
1128 try { AddToList(objOrList, AsBigInteger(value), count++); }
1129 catch { GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Invalid BigInteger format [{value}] for element type [{elementType}]", kvp, GPALObjectType.Other); }
1132 else if (IsVersionType(elementType))
1134 if (value is
string versionStr && System.Version.TryParse(versionStr, out System.Version version))
1136 AddToList(objOrList, version, count++);
1140 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Invalid Version format [{value}] for element type [{elementType}]", kvp, GPALObjectType.Other);
1143 else if (
true == IsIpAddressType(elementType))
1145 if (value is
string ipStr && IPAddress.TryParse(ipStr, out IPAddress ipAddress))
1147 AddToList(objOrList, ipAddress, count++);
1151 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Invalid IPAddress format [{value}] for element type [{elementType}]", kvp, GPALObjectType.Other);
1154 else if (
true == IsUriType(elementType))
1156 if (value is
string uriStr && Uri.TryCreate(uriStr, UriKind.Absolute, out Uri uri))
1158 AddToList(objOrList, uri, count++);
1162 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Invalid Uri format [{value}] for element type [{elementType}]", kvp, GPALObjectType.Other);
1165 else if (
true == IsGuidType(elementType))
1167 if (value is
string guidStr && IsNullableType(elementType) && guidStr.Trim().Equals(
"null", StringComparison.OrdinalIgnoreCase))
1168 AddToList(objOrList,
null, count++);
1169 else if (value is
string guidStr2 && Guid.TryParse(guidStr2, out Guid guid))
1170 AddToList(objOrList, guid, count++);
1172 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Invalid Guid format [{value}] for element type [{elementType}]", kvp, GPALObjectType.Other);
1174 else if (
true == IsTimeSpanType(elementType))
1176 if (value is
string timeSpanStr && IsNullableType(elementType) && timeSpanStr.Trim().Equals(
"null", StringComparison.OrdinalIgnoreCase))
1177 AddToList(objOrList,
null, count++);
1178 else if (value is
string timeSpanStr2 && TimeSpan.TryParse(timeSpanStr2, out TimeSpan timeSpan))
1179 AddToList(objOrList, timeSpan, count++);
1181 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Invalid TimeSpan format [{value}] for element type [{elementType}]", kvp, GPALObjectType.Other);
1183 else if (IsClassType(elementType) && elementType != typeof(
string))
1186 var converter = TypeDescriptor.GetConverter(elementType);
1187 if (converter !=
null && converter.CanConvertFrom(value.GetType()))
1189 value = converter.ConvertFrom(value);
1190 AddToList(objOrList, value, count++);
1194 AddToList(objOrList, value, count++);
1200 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Unsupported value type [{value.GetType()}] for element type [{elementType}]", kvp, GPALObjectType.Other);
1203 catch (Exception ex)
1205 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Failed to add value to enumerable [{targetType}]", kvp, GPALObjectType.Other, ex);
1211 else if (dictionary is IEnumerable && !IsDictionaryType(dictionary.GetType()))
1213 IEnumerable enumInput = (IEnumerable)dictionary;
1214 foreach (dynamic item
in enumInput)
1218 dynamic value = item;
1221 if (elementType.IsValueType && !IsNullableType(elementType))
1223 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Null value not allowed for non-nullable type [{elementType}]", item, GPALObjectType.Other);
1227 AddToList(objOrList,
null, count++);
1230 else if (elementType.IsAssignableFrom(value.GetType()))
1232 AddToList(objOrList, value, count++);
1234 else if (elementType.IsArray)
1236 var arrayElementType = elementType.GetElementType();
1237 var listType = typeof(List<>).MakeGenericType(arrayElementType);
1238 var tempList = (IList)InstantiateOne(listType);
1239 if (value is IEnumerable arrayValues)
1241 foreach (var arrayItem
in arrayValues)
1243 var convertedItem = CreateObjectFromDictionary(arrayItem, arrayElementType, enumInput, columnNames);
1244 tempList.Add(convertedItem);
1247 value = tempList.Cast<
object>().ToArray();
1248 AddToList(objOrList, value, count++);
1250 else if (IsEnumerableType(elementType) && value is IEnumerable enumValue)
1252 value = CreateObjectFromDictionary(enumValue, elementType, enumInput, columnNames);
1253 AddToList(objOrList, value, count++);
1255 else if (IsComplexType(elementType))
1257 if (value is
string complexStr)
1259 value = ParseComplex(complexStr);
1260 AddToList(objOrList, value, count++);
1262 else if (value is Complex complexValue)
1264 AddToList(objOrList, complexValue, count++);
1266 else if (value is IDictionary || (value is IEnumerable && !(value is
string)))
1268 AddToList(objOrList, ComplexFromDict(value), count++);
1272 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Value type [{value.GetType()}] incompatible with complex/nullable element type [{elementType}]", value, GPALObjectType.Other);
1275 else if (IsBigIntegerType(elementType))
1277 if (value is
string biNullStr4 && IsNullableType(elementType) && biNullStr4.Trim().Equals(
"null", StringComparison.OrdinalIgnoreCase))
1278 AddToList(objOrList,
null, count++);
1281 try { AddToList(objOrList, AsBigInteger(value), count++); }
1282 catch { GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Invalid BigInteger format [{value}] for element type [{elementType}]", item, GPALObjectType.Other); }
1285 else if (IsVersionType(elementType))
1287 if (value is
string versionStr && System.Version.TryParse(versionStr, out System.Version version))
1289 AddToList(objOrList, version, count++);
1293 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Invalid Version format [{value}] for element type [{elementType}]", item, GPALObjectType.Other);
1296 else if (
true == IsIpAddressType(elementType))
1298 if (value is
string ipStr && IPAddress.TryParse(ipStr, out IPAddress ipAddress))
1300 AddToList(objOrList, ipAddress, count++);
1304 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Invalid IPAddress format [{value}] for element type [{elementType}]", item, GPALObjectType.Other);
1307 else if (
true == IsUriType(elementType))
1309 if (value is
string uriStr && Uri.TryCreate(uriStr, UriKind.Absolute, out Uri uri))
1311 AddToList(objOrList, uri, count++);
1315 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Invalid Uri format [{value}] for element type [{elementType}]", item, GPALObjectType.Other);
1318 else if (
true == IsGuidType(elementType))
1320 if (value is
string guidStr && IsNullableType(elementType) && guidStr.Trim().Equals(
"null", StringComparison.OrdinalIgnoreCase))
1321 AddToList(objOrList,
null, count++);
1322 else if (value is
string guidStr2 && Guid.TryParse(guidStr2, out Guid guid))
1323 AddToList(objOrList, guid, count++);
1325 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Invalid Guid format [{value}] for element type [{elementType}]", item, GPALObjectType.Other);
1327 else if (
true == IsTimeSpanType(elementType))
1329 if (value is
string timeSpanStr && IsNullableType(elementType) && timeSpanStr.Trim().Equals(
"null", StringComparison.OrdinalIgnoreCase))
1330 AddToList(objOrList,
null, count++);
1331 else if (value is
string timeSpanStr2 && TimeSpan.TryParse(timeSpanStr2, out TimeSpan timeSpan))
1332 AddToList(objOrList, timeSpan, count++);
1334 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Invalid TimeSpan format [{value}] for element type [{elementType}]", item, GPALObjectType.Other);
1336 else if (
true == IsBooleanType(elementType))
1338 if (value is
string boolStr)
1340 if (IsNullableType(elementType) && boolStr.Trim().Equals(
"null", StringComparison.OrdinalIgnoreCase))
1342 AddToList(objOrList,
null, count++);
1344 else if (
bool.TryParse(boolStr.Trim(), out
bool parsedBool))
1346 AddToList(objOrList, parsedBool, count++);
1351 string lower = boolStr.Trim().ToLowerInvariant();
1352 if (lower ==
"1" || lower ==
"yes" || lower ==
"on" || lower ==
"true")
1354 AddToList(objOrList,
true, count++);
1356 else if (lower ==
"0" || lower ==
"no" || lower ==
"off" || lower ==
"false")
1358 AddToList(objOrList,
false, count++);
1362 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
1363 $
"Invalid boolean format [{value}] for element type [{elementType}]",
1364 item, GPALObjectType.Other);
1368 else if (value is
bool directBool)
1370 AddToList(objOrList, directBool, count++);
1374 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
1375 $
"Invalid type for boolean: expected string or bool, got [{value?.GetType()}]",
1376 item, GPALObjectType.Other);
1379 else if (IsSimpleType(elementType) || IsNullableType(elementType))
1381 if (IsSimpleType(value.GetType()) || IsNullableType(value.GetType()))
1383 value = ConvertValue(value, elementType);
1384 AddToList(objOrList, value, count++);
1388 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Value type [{value.GetType()}] incompatible with simple/nullable element type [{elementType}]", item, GPALObjectType.Other);
1391 else if (IsClassType(elementType) && elementType != typeof(
string))
1393 var converter = TypeDescriptor.GetConverter(elementType);
1394 if (converter !=
null && converter.CanConvertFrom(value.GetType()))
1396 value = converter.ConvertFrom(value);
1397 AddToList(objOrList, value, count++);
1403 AddToList(objOrList, value, count++);
1407 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Unsupported class type [{value.GetType()}] for element type [{elementType}]", item, GPALObjectType.Other);
1411 else if (IsCustomStructType(elementType))
1414 if (value is IList svl2 && svl2.Count == 1 && svl2[0] is IDictionary svd2)
1416 if (value is IDictionary svStructDict2)
1418 value = CreateObjectFromDictionary(svStructDict2, elementType, enumInput, columnNames);
1419 AddToList(objOrList, value, count++);
1423 var converter = TypeDescriptor.GetConverter(elementType);
1424 if (converter !=
null && converter.CanConvertFrom(value.GetType()))
1426 value = converter.ConvertFrom(value);
1427 AddToList(objOrList, value, count++);
1429 else if (
false == IsSimpleType(value.GetType()))
1433 AddToList(objOrList, value, count++);
1437 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Unsupported struct type [{value.GetType()}] for element type [{elementType}]", item, GPALObjectType.Other);
1442 else if (value is IDictionary nestedDict)
1444 if (IsClassType(elementType) && elementType != typeof(
string))
1446 value = ConvertToClass(nestedDict, elementType, enumInput, columnNames);
1447 AddToList(objOrList, value, count++);
1449 else if (IsDictionaryType(elementType) || IsTupleType(elementType))
1451 value = CreateObjectFromDictionary(nestedDict, elementType, enumInput, columnNames);
1452 AddToList(objOrList, value, count++);
1456 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Dictionary value [{value.GetType()}] incompatible with element type [{elementType}]", item, GPALObjectType.Other);
1461 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Unsupported item type [{value?.GetType()}] for element type [{elementType}]", item, GPALObjectType.Other);
1464 catch (Exception ex)
1466 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Failed to add item to enumerable [{targetType}]", item, GPALObjectType.Other, ex);
1471 else if (!(dictionary is IDictionary inputDict))
1473 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Expected IDictionary input for [{targetType}]", dictionary, GPALObjectType.None);
1478 foreach (dynamic kvp
in inputDict)
1480 noPropertyOrField = 0;
1481 priorKvpPropertyType = kvpPropertyType;
1483 if (IsKeyValuePairType(kvp.GetType()))
1485 if (
null != columnNames && columnIdx < columnNames.Count)
1486 kvpName = columnNames[columnIdx++].Trim(
'"');
1489 string rawKey = kvp.Key?.ToString().Trim(
'"') ??
string.Empty;
1490 if (rawKey.StartsWith(
"GPALKEY", StringComparison.Ordinal))
1491 rawKey = rawKey.Substring(7);
1492 else if (System.Text.RegularExpressions.Regex.IsMatch(rawKey,
@"^GPAL\d+_"))
1493 rawKey = System.Text.RegularExpressions.Regex.Replace(rawKey,
@"^GPAL\d+_",
"");
1497 if (typeof(
string) == kvp.Value?.GetType())
1498 kvpValue = kvp.Value?.ToString().Trim(
'"');
1500 kvpValue = kvp.Value;
1502 kvpProperty = targetType.GetProperty(kvpName)
1503 ?? targetType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
1504 .FirstOrDefault(p => p.Name.Equals(kvpName, StringComparison.OrdinalIgnoreCase));
1506 kvpField = targetType.GetField(kvpName)
1507 ?? targetType.GetFields(BindingFlags.Public | BindingFlags.Instance)
1508 .FirstOrDefault(f => f.Name.Equals(kvpName, StringComparison.OrdinalIgnoreCase));
1510 if (kvpProperty !=
null)
1512 kvpPropertyType = kvpProperty.PropertyType;
1513 kvpPropertyOrFieldName = kvpProperty.Name;
1515 if (kvpField !=
null)
1517 kvpFieldType = kvpField.FieldType;
1518 kvpPropertyOrFieldName = kvpField.Name;
1526 if (kvpProperty ==
null && kvpField ==
null)
1528 kvpPropertyType = targetType;
1529 noPropertyOrField = 2;
1532 if (kvpProperty !=
null && kvpProperty.CanWrite && kvpProperty.Name.Equals(kvpName, StringComparison.OrdinalIgnoreCase))
1534 if (IsDictionaryType(kvpPropertyType))
1536 if (kvpValue is IList kvpSingleWrap && kvpSingleWrap.Count == 1 && kvpSingleWrap[0] is IDictionary kvpUnwrappedInner)
1537 kvpValue = kvpUnwrappedInner;
1538 if (kvpValue ==
null || (kvpValue is
string kvpDictNullStr && kvpDictNullStr.Trim().Equals(
"null", StringComparison.OrdinalIgnoreCase)))
1542 else if (kvpValue is IDictionary valueDict)
1544 IDictionary propDict = (IDictionary)ConverterHelper.InstantiateOne(kvpPropertyType);
1545 Type keyType = kvpPropertyType.GetGenericArguments()[0];
1546 Type valueType = kvpPropertyType.GetGenericArguments()[1];
1548 foreach (dynamic valueKvp
in valueDict)
1552 object key = IsSimpleType(keyType)
1553 ? ResolveSimpleDictionaryKey(valueKvp.Key, keyType)
1554 : (valueKvp.Key is IDictionary valueKeyDict ? CreateObjectFromDictionary(valueKeyDict, keyType, valueDict, null) : valueKvp.Key);
1557 if (IsKeyValuePairType(valueKvp.Value?.GetType()))
1559 var nestedKvp = valueKvp.Value;
1560 value = IsDictionaryType(valueType)
1561 ? CreateObjectFromDictionary(new Dictionary<object, object> { { nestedKvp.Key, nestedKvp.Value } }, valueType, valueDict, columnNames)
1562 : ConvertValue(nestedKvp.Value, valueType,
null, kvpName);
1564 else if (IsDictionaryType(valueKvp.Value?.GetType()))
1569 if (valueType == typeof(
object) || valueType.IsAssignableFrom(valueKvp.Value.GetType()))
1570 value = valueKvp.Value;
1572 value = CreateObjectFromDictionary(valueKvp.Value, valueType, valueDict, columnNames);
1576 value = ConvertValue(valueKvp.Value, valueType,
null, kvpName);
1579 if (value ==
null || valueType.IsAssignableFrom(value.GetType()))
1580 propDict[key] = value;
1582 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Unable to convert [{DescribeForLog(value)}] to [{valueType.Name}] for [{kvpName}]", valueKvp, GPALObjectType.None);
1584 catch (Exception ex)
1586 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Failed to process key-value pair for [{kvpName}]", valueKvp, GPALObjectType.None, ex);
1589 if (objOrList.GetType().IsValueType)
1591 string bfName = $
"<{kvpProperty.Name}>k__BackingField";
1592 objOrList = GetSetStructMethod(objOrList.GetType()).Invoke(
null,
new object[] { (object)objOrList, bfName, propDict });
1595 kvpProperty.SetValue(objOrList, propDict);
1599 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Value for property [{kvpName}] is not a dictionary", kvpValue, GPALObjectType.None);
1602 else if (IsEnumerableType(kvpPropertyType) || kvpPropertyType.IsArray)
1604 Type elementType = kvpPropertyType.IsArray ? kvpPropertyType.GetElementType() : kvpPropertyType.GetGenericArguments()[0];
1605 itemobj = ConverterHelper.InstantiateOne(elementType);
1608 if (kvpValue is
string kvpNullCollStr && kvpNullCollStr.Trim().Equals(
"null", StringComparison.OrdinalIgnoreCase))
1612 else if (kvpValue is IDictionary collapsedDict && !IsDictionaryType(elementType) && (IsClassType(elementType) || IsCustomStructType(elementType) || IsTupleType(elementType)))
1618 bool looksLikeIndexedArray = collapsedDict.Keys.Cast<
object>().All(k =>
int.TryParse(k?.ToString(), out _));
1619 object innerList = ConverterHelper.InstantiateOne(kvpPropertyType, looksLikeIndexedArray ? collapsedDict.Count : 1);
1620 if (looksLikeIndexedArray)
1623 foreach (var indexedKey
in collapsedDict.Keys)
1625 object indexedElement = collapsedDict[indexedKey] is IDictionary indexedElementDict
1626 ? CreateObjectFromDictionary(indexedElementDict, elementType, parentDictionary, columnNames)
1627 : ConvertValue(collapsedDict[indexedKey], elementType);
1628 AddToList(innerList, indexedElement, indexedIdx++);
1633 object element = CreateObjectFromDictionary(collapsedDict, elementType, parentDictionary, columnNames);
1634 AddToList(innerList, element, 0);
1636 if (ObjectCopier.GetItemCount(kvpProperty, innerList) > 0)
1638 if (IsEnumerableObject(objOrList) || objOrList.GetType().IsArray)
1639 AddToList(objOrList, innerList, count++);
1640 else if (objOrList.GetType().IsValueType)
1642 string bfName = $
"<{kvpProperty.Name}>k__BackingField";
1643 objOrList = GetSetStructMethod(objOrList.GetType()).Invoke(
null,
new object[] { (object)objOrList, bfName, innerList });
1646 kvpProperty.SetValue(objOrList, innerList);
1649 else if (IsEnumerableObject(kvpValue) || IsArray(kvpValue))
1652 Type innerElementType =
null;
1656 innerElementType = kvpValue.IsArray ? kvpValue.GetElementType() : kvpValue.GetGenericArguments()[0];
1664 innerElementType = IsTupleType(elementType) ? null : elementType.GetGenericArguments()[0];
1668 innerElementType = elementType;
1672 object innerList = ConverterHelper.InstantiateOne(kvpPropertyType, ObjectCopier.GetItemCount(kvpProperty, kvpValue));
1673 foreach (dynamic kvpValueItem
in (IEnumerable)kvpValue)
1675 Type kvpValueItemType = kvpValueItem.GetType();
1676 object element =
null;
1678 if (
false == IsKeyValuePairType(kvpValueItemType) &&
false == IsCustomStructType(kvpValueItemType) && kvpValueItem ==
null)
1680 if (elementType.IsValueType && !IsNullableType(elementType))
1682 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Null value not allowed for non-nullable type [{elementType}]", kvpValueItem, GPALObjectType.Other);
1686 AddToList(innerList,
null, count++);
1689 else if (elementType.IsAssignableFrom(kvpValueItem.GetType()))
1691 AddToList(innerList, kvpValueItem, count++);
1693 else if (kvpValueItem is IDictionary nestedDict)
1695 if ((IsCustomStructType(elementType) || IsClassType(elementType)) && elementType != typeof(
string))
1697 element = ConvertToClass(nestedDict, elementType, parentDictionary, columnNames);
1698 AddToList(innerList, element, count++);
1700 else if (IsDictionaryType(kvpValueItem.GetType()) || IsDictionaryType(elementType) ||
true == IsEnumerableType(elementType))
1702 element = CreateObjectFromDictionary(nestedDict, elementType, parentDictionary, columnNames);
1703 AddToList(innerList, element, count++);
1707 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Dictionary value [{kvpValueItem.GetType()}] incompatible with element type [{elementType}]", kvpValueItem, GPALObjectType.Other);
1710 else if (IsKeyValuePairType(kvpValueItemType))
1712 var nestedKvp = kvpValueItem;
1713 Type valueType = elementType.IsGenericType && elementType.GetGenericTypeDefinition() == typeof(Dictionary<,>)
1714 ? elementType.GetGenericArguments()[1]
1717 if (IsDictionaryType(elementType))
1719 element = CreateObjectFromDictionary(
new Dictionary<object, object> { { nestedKvp.Key, nestedKvp.Value } }, elementType, parentDictionary, columnNames);
1721 else if (IsComplexType(valueType))
1723 element = ParseComplex(nestedKvp.Value?.ToString());
1725 else if (IsSimpleType(valueType))
1727 element = ConvertValue(nestedKvp.Value?.ToString(), valueType,
null, kvpName);
1733 element = CreateObjectFromDictionary(nestedKvp.Value, elementType, parentDictionary, columnNames);
1737 element = nestedKvp.Value;
1740 AddToList(innerList, element, count++);
1742 else if (IsTupleType(elementType) && kvpValueItem is IList tupleBundledList)
1746 foreach (dynamic tupleSubItem
in tupleBundledList)
1748 if (tupleSubItem is IDictionary tupleSubDict)
1750 element = CreateObjectFromDictionary(tupleSubDict, elementType, parentDictionary, columnNames);
1751 AddToList(innerList, element, count++);
1755 else if (IsComplexType(elementType) || (
true == IsComplexType(innerElementType) && typeof(
string) == kvpValueItemType))
1757 if (kvpValueItem is
string complexStr)
1759 element = ParseComplex(complexStr);
1760 AddToList(innerList, element, count++);
1762 else if (kvpValueItem is Complex complexValue)
1764 AddToList(innerList, complexValue, count++);
1766 else if (
true == IsDictionaryType(kvpValueItemType) ||
true == IsEnumerableType(kvpValueItemType))
1769 element = CreateObjectFromDictionary(kvpValueItem, elementType, kvpValue, columnNames);
1770 AddToList(innerList, element, count++);
1775 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Invalid Complex format [{kvpValueItem}] for element type [{elementType}]", kvpValueItem, GPALObjectType.Other);
1778 else if (((IsDictionaryType(kvpValueItemType) || IsEnumerableType(elementType) || elementType.IsArray)) && typeof(
string) != kvpValueItemType)
1780 if (IsEnumerableType(elementType) && !(kvpValueItem is IEnumerable))
1783 Type innerScalarType = elementType.IsGenericType ? elementType.GetGenericArguments()[0] : typeof(
object);
1784 dynamic singleItemList = InstantiateOne(elementType);
1785 AddToList(singleItemList, ConvertValue(kvpValueItem, innerScalarType,
null, kvpName), 0);
1786 element = singleItemList;
1790 element = CreateObjectFromDictionary(kvpValueItem, elementType, parentDictionary, columnNames);
1792 AddToList(innerList, element, count++);
1794 else if (IsBigIntegerType(elementType) || IsBigIntegerType(innerElementType))
1796 Type biNullableCheck2 = IsBigIntegerType(innerElementType) ? innerElementType : elementType;
1797 if (kvpValueItem is
string biNullStr3 && IsNullableType(biNullableCheck2) && biNullStr3.Trim().Equals(
"null", StringComparison.OrdinalIgnoreCase))
1798 AddToList(innerList,
null, count++);
1801 try { AddToList(innerList, AsBigInteger(kvpValueItem), count++); }
1802 catch { GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Invalid BigInteger format [{kvpValueItem}] for element type [{elementType}]", kvpValueItem, GPALObjectType.Other); }
1805 else if (IsVersionType(elementType) || IsVersionType(innerElementType))
1807 if (kvpValueItem is
string versionStr && System.Version.TryParse(versionStr, out System.Version version))
1809 AddToList(innerList, version, count++);
1813 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Invalid Version format [{kvpValueItem}] for element type [{elementType}]", kvpValueItem, GPALObjectType.Other);
1816 else if (
true == IsIpAddressType(elementType) ||
true == IsIpAddressType(innerElementType))
1818 if (kvpValueItem is
string ipStr && IPAddress.TryParse(ipStr, out IPAddress ipAddress))
1820 AddToList(innerList, ipAddress, count++);
1824 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Invalid IPAddress format [{kvpValueItem}] for element type [{elementType}]", kvpValueItem, GPALObjectType.Other);
1827 else if (
true == IsUriType(elementType) ||
true == IsUriType(innerElementType) )
1829 if (kvpValueItem is
string uriStr && Uri.TryCreate(uriStr, UriKind.Absolute, out Uri uri))
1831 AddToList(innerList, uri, count++);
1835 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Invalid Uri format [{kvpValueItem}] for element type [{elementType}]", kvpValueItem, GPALObjectType.Other);
1838 else if (
true == IsGuidType(elementType) ||
true == IsGuidType(innerElementType))
1840 Type gNullableCheck = IsGuidType(innerElementType) ? innerElementType : elementType;
1841 if (kvpValueItem is
string guidNullStr && IsNullableType(gNullableCheck) && guidNullStr.Trim().Equals(
"null", StringComparison.OrdinalIgnoreCase))
1842 AddToList(innerList,
null, count++);
1843 else if (kvpValueItem is
string guidStr && Guid.TryParse(guidStr, out Guid guid))
1844 AddToList(innerList, guid, count++);
1846 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Invalid Guid format [{kvpValueItem}] for element type [{elementType}]", kvpValueItem, GPALObjectType.Other);
1848 else if (
true == IsTimeSpanType(elementType) ||
true == IsTimeSpanType(innerElementType))
1850 Type tsNullableCheck = IsTimeSpanType(innerElementType) ? innerElementType : elementType;
1851 if (kvpValueItem is
string tsNullStr && IsNullableType(tsNullableCheck) && tsNullStr.Trim().Equals(
"null", StringComparison.OrdinalIgnoreCase))
1852 AddToList(innerList,
null, count++);
1853 else if (kvpValueItem is
string timeSpanStr && TimeSpan.TryParse(timeSpanStr, out TimeSpan timeSpan))
1854 AddToList(innerList, timeSpan, count++);
1856 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Invalid TimeSpan format [{kvpValueItem}] for element type [{elementType}]", kvpValueItem, GPALObjectType.Other);
1859 else if (IsCustomStructType(elementType) || IsCustomStructType(innerElementType))
1861 Type csElemType = IsCustomStructType(innerElementType) ? innerElementType : elementType;
1863 dynamic csItem = kvpValueItem;
1864 if (csItem is IList csl3 && csl3.Count == 1 && csl3[0] is IDictionary csd3)
1866 if (csItem is IDictionary csStructDict3)
1868 element = CreateObjectFromDictionary(csStructDict3, csElemType, parentDictionary, columnNames);
1869 AddToList(innerList, element, count++);
1873 var converter = TypeDescriptor.GetConverter(csElemType);
1874 if (converter !=
null && converter.CanConvertFrom(csItem.GetType()))
1876 element = converter.ConvertFrom(csItem);
1877 AddToList(innerList, element, count++);
1879 else if (
false == IsSimpleType(csItem.GetType()))
1883 AddToList(innerList, csItem, count++);
1887 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Unsupported struct type [{csItem.GetType()}] for element type [{elementType}]", kvpValueItem, GPALObjectType.Other);
1892 else if (IsSimpleType(elementType) || IsNullableType(elementType))
1894 if (IsSimpleType(kvpValueItem.GetType()) || IsNullableType(kvpValueItem.GetType()))
1896 element = ConvertValue(kvpValueItem, elementType,
null, kvpName);
1897 AddToList(innerList, element, count++);
1901 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Value type [{kvpValueItem.GetType()}] incompatible with simple/nullable element type [{elementType}]", kvpValueItem, GPALObjectType.Other);
1904 else if (IsClassType(elementType) && elementType != typeof(
string))
1906 var converter = TypeDescriptor.GetConverter(elementType);
1907 if (converter !=
null && converter.CanConvertFrom(kvpValueItem.GetType()))
1909 element = converter.ConvertFrom(kvpValueItem);
1910 AddToList(innerList, element, count++);
1916 AddToList(innerList, kvpValueItem, count++);
1920 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Unsupported class type [{kvpValueItem.GetType()}] for element type [{elementType}]", kvpValueItem, GPALObjectType.Other);
1926 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Unsupported item type [{kvpValueItem?.GetType()}] for element type [{elementType}]", kvpValueItem, GPALObjectType.Other);
1930 if (ObjectCopier.GetItemCount(kvpProperty, innerList) > 0)
1932 if (IsEnumerableObject(objOrList) || objOrList.GetType().IsArray)
1933 AddToList(objOrList, innerList, count++);
1934 else if (objOrList.GetType().IsValueType)
1936 string bfName = $
"<{kvpProperty.Name}>k__BackingField";
1937 objOrList = GetSetStructMethod(objOrList.GetType()).Invoke(
null,
new object[] { (object)objOrList, bfName, innerList });
1940 kvpProperty.SetValue(objOrList, innerList);
1944 string currentErrorMessage = $
"No supplied values for [{kvpProperty?.Name}]";
1945 if (
false == lastErrorMessage.Contains(currentErrorMessage))
1947 lastErrorMessage.Add(currentErrorMessage);
1948 GPAL.PublishSimpleEvent(GPALEventType.ERROR, currentErrorMessage, objOrList, GPALObjectType.Other);
1949 supressedMessage =
false;
1951 else if (
false == supressedMessage)
1953 GPAL.PublishSimpleEvent(GPALEventType.INFO,
"Supressing repeat messages", objOrList, GPALObjectType.Other);
1954 supressedMessage =
true;
1958 else if (IsComplexType(elementType))
1960 if (kvpValue !=
null)
1962 itemobj = ParseComplex(kvpValue.ToString());
1963 AddToList(objOrList, itemobj, count++);
1972 SetValue(ref objOrList, kvpProperty, kvpValue, kvpPropertyType, parentDictionary, columnNames);
1975 else if (IsCustomStructType(kvpPropertyType))
1978 IDictionary structKvpDict = kvpValue as IDictionary;
1979 if (structKvpDict ==
null && kvpValue is IList svl && svl.Count == 1 && svl[0] is IDictionary svd)
1980 structKvpDict = svd;
1981 if (structKvpDict !=
null)
1983 object structObj = CreateObjectFromDictionary(structKvpDict, kvpPropertyType, parentDictionary, columnNames);
1984 if (objOrList.GetType().IsValueType)
1986 string bfName = $
"<{kvpProperty.Name}>k__BackingField";
1987 objOrList = GetSetStructMethod(objOrList.GetType()).Invoke(
null,
new object[] { (object)objOrList, bfName, structObj });
1990 kvpProperty.SetValue(objOrList, structObj);
1993 else if (IsComplexType(kvpPropertyType) || IsNullableType(kvpPropertyType) || IsSimpleType(kvpPropertyType))
1995 SetValue(ref objOrList, kvpProperty, kvpValue, kvpPropertyType, parentDictionary, columnNames);
1997 else if (kvpProperty.Name.Equals(kvpName, StringComparison.OrdinalIgnoreCase))
1999 SetValue(ref objOrList, kvpProperty, kvpValue, kvpPropertyType, parentDictionary, columnNames);
2001 else if (kvpValue !=
null && (IsDictionaryType(kvpValue.GetType()) || IsEnumerableType(kvpValue.GetType())))
2003 objOrList = CreateObjectFromDictionary(kvpValue, targetType, kvpValue, columnNames);
2007 noPropertyOrField++;
2010 else if (kvpProperty !=
null && !kvpProperty.CanWrite && kvpProperty.Name.Equals(kvpName, StringComparison.OrdinalIgnoreCase))
2012 if (
false == targetType.Name.StartsWith(
"GPAL"))
2013 GPAL.PublishSimpleEvent(GPALEventType.DEBUG, $
"[{targetType.Name}][{kvpName}] 'CanWrite' [false]", objOrList, GPALObjectType.Other);
2015 else if (kvpField !=
null && kvpField.Name.Equals(kvpName, StringComparison.OrdinalIgnoreCase))
2017 if (IsEnumerableType(kvpFieldType) || kvpFieldType.IsArray)
2019 Type elementType = kvpFieldType.IsArray ? kvpFieldType.GetElementType() : kvpFieldType.GetGenericArguments()[0];
2020 itemobj = ConverterHelper.InstantiateOne(elementType);
2023 if (IsEnumerableObject(kvpValue) || IsArray(kvpValue))
2025 object innerList = ConverterHelper.InstantiateOne(kvpFieldType, ObjectCopier.GetItemCount(kvpProperty, kvpValue));
2026 foreach (dynamic kvpValueItem
in (IEnumerable)kvpValue)
2028 Type kvpValueItemType = kvpValueItem.GetType();
2029 object element =
null;
2031 if (IsKeyValuePairType(kvpValueItemType))
2033 var nestedKvp = kvpValueItem;
2034 Type valueType = elementType.IsGenericType && elementType.GetGenericTypeDefinition() == typeof(Dictionary<,>)
2035 ? elementType.GetGenericArguments()[1]
2038 if (IsDictionaryType(elementType))
2040 element = CreateObjectFromDictionary(
new Dictionary<object, object> { { nestedKvp.Key, nestedKvp.Value } }, elementType, kvpValue, columnNames);
2042 else if (IsComplexType(valueType))
2044 element = ParseComplex(nestedKvp.Value?.ToString());
2046 else if (IsSimpleType(valueType))
2048 element = ConvertValue(kvp.Value?.ToString().Trim(
'"'), valueType);
2052 element = nestedKvp.Value;
2054 AddToList(innerList, element, count++);
2056 else if (IsDictionaryType(kvpValueItemType) || IsEnumerableType(elementType) || elementType.IsArray)
2058 element = CreateObjectFromDictionary(kvpValueItem, elementType, kvpValue, columnNames);
2059 AddToList(innerList, element, count++);
2061 else if (IsComplexType(elementType))
2063 element = ParseComplex(kvpValueItem.ToString());
2064 AddToList(innerList, element, count++);
2066 else if (IsSimpleType(elementType))
2068 element = ConvertValue(kvpValueItem.ToString(), elementType);
2069 AddToList(innerList, Convert.ChangeType(element, elementType), count++);
2073 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Skipping unexpected item type [{kvpValueItemType.Name}] for [{elementType.Name}]", kvpValueItem, GPALObjectType.None);
2077 if (ObjectCopier.GetItemCount(kvpProperty, innerList) > 0)
2079 if (IsEnumerableObject(objOrList) || objOrList.GetType().IsArray)
2080 AddToList(objOrList, innerList, count++);
2081 else if (objOrList.GetType().IsValueType)
2082 objOrList = GetSetStructMethod(objOrList.GetType()).Invoke(
null,
new object[] { (object)objOrList, kvpField.Name, (
object)innerList });
2084 kvpField.SetValue(objOrList, innerList);
2088 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $
"No values for [{kvpField.Name}]", objOrList, GPALObjectType.Other);
2091 else if (IsComplexType(kvpFieldType))
2093 itemobj = ParseComplex(kvpValue.ToString());
2094 AddToList(objOrList, itemobj, count++);
2096 else if (IsSimpleType(kvpFieldType))
2098 itemobj = ConvertValue(kvpValue.ToString(), kvpFieldType);
2099 SetValue(ref objOrList, kvpField, itemobj, kvpFieldType, parentDictionary, columnNames);
2103 SetValue(ref objOrList, kvpField, kvpValue, kvpFieldType, parentDictionary, columnNames);
2106 else if (IsCustomStructType(kvpFieldType) && kvpValue is IDictionary)
2108 object structObj = CreateObjectFromDictionary(kvpValue, kvpFieldType, parentDictionary, columnNames);
2109 kvpField.SetValue(objOrList, structObj);
2111 else if (IsComplexType(kvpFieldType) || IsNullableType(kvpFieldType) || IsSimpleType(kvpFieldType))
2113 SetValue(ref objOrList, kvpField, kvpValue, kvpFieldType, parentDictionary, columnNames);
2115 else if (kvpField.Name.Equals(kvpName, StringComparison.OrdinalIgnoreCase))
2117 SetValue(ref objOrList, kvpField, kvpValue, kvpFieldType, parentDictionary, columnNames);
2121 noPropertyOrField++;
2144 if (noPropertyOrField == 2)
2148 string currentErrorMessage = $
"No property/field [{kvpName}] in [{targetType.Name}]";
2149 if (
false == lastErrorMessage.Contains(currentErrorMessage))
2151 lastErrorMessage.Add(currentErrorMessage);
2152 GPAL.PublishSimpleEvent(GPALEventType.WARNING, currentErrorMessage, objOrList, GPALObjectType.Other);
2153 supressedMessage =
false;
2155 else if (
false == supressedMessage)
2157 GPAL.PublishSimpleEvent(GPALEventType.INFO,
"Supressing repeat messages", objOrList, GPALObjectType.Other);
2158 supressedMessage =
true;
2164 if (addedToVisited) _dictVisited.Remove((
object)dictionary);
2165 if (isRoot) _dictVisited =
null;
2168 private static readonly Dictionary<Type, MethodInfo> _setStructMethodCache =
new Dictionary<Type, MethodInfo>();
2169 private static MethodInfo GetSetStructMethod(Type structType)
2171 if (!_setStructMethodCache.TryGetValue(structType, out var m))
2174 .GetMethod(
"SetStructBackingField", BindingFlags.NonPublic | BindingFlags.Static)
2175 .MakeGenericMethod(structType);
2176 _setStructMethodCache[structType] = m;
2185 private static object SetStructBackingField<T>(T instance,
string fieldName,
object value) where T :
struct
2187 FieldInfo field = typeof(T).GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance)
2188 ?? typeof(T).GetField(fieldName, BindingFlags.Public | BindingFlags.Instance);
2190 field.SetValueDirect(__makeref(instance), value);
2193 private static bool IsList(
object obj)
2195 return obj is IList && obj.GetType().IsGenericType;
2197 private static bool IsArray(
object obj)
2199 return obj !=
null && obj.GetType().IsArray;
2202 static object SetValueDirect(FieldInfo field,
object instance,
object value)
2204 field.SetValueDirect(__makeref(instance), value);
2219 internal static void SetValue(ref dynamic instance, dynamic propertyOrField, dynamic propertyValue, Type propertyType, dynamic parentDictionary, List<string> columnNames)
2224 if (propertyValue is
string recursionMarker && propertyType != typeof(
string)
2225 && recursionMarker.StartsWith(
"Recursion to [") && recursionMarker.EndsWith(
"]"))
2231 if (propertyValue is IList svSingleList && svSingleList.Count == 1
2232 && svSingleList[0] is IDictionary svInnerDict
2233 && !IsEnumerableType(propertyType) && !propertyType.IsArray && !IsDictionaryType(propertyType))
2235 propertyValue = svInnerDict;
2238 dynamic entryValue =
null;
2239 string propertyTypeName = propertyType.Name;
2245 if ((IsEnumerableType(propertyType) || propertyType.IsArray) && !IsDictionaryType(propertyType))
2248 if (propertyValue is IList pvList && pvList.Count == 1 && pvList[0] is IDictionary pvInner && pvInner.Count == 1)
2250 foreach (DictionaryEntry xw
in pvInner)
2252 propertyValue = xw.Value is IList ? xw.Value :
new List<object> { xw.Value };
2257 else if (propertyValue is IDictionary xmlWrapperDict && xmlWrapperDict.Count == 1)
2259 foreach (DictionaryEntry xw
in xmlWrapperDict)
2261 propertyValue = xw.Value is IList ? xw.Value :
new List<object> { xw.Value };
2267 if (typeof(IPAddress) == propertyType)
2269 if (propertyValue is IDictionary ipDict)
2271 foreach (var v
in ipDict.Values) { entryValue = v;
break; }
2274 entryValue = propertyValue?.ToString();
2276 else if (IsTupleType(propertyType) && propertyValue is IDictionary)
2280 object tupleValue = CreateObjectFromDictionary(propertyValue, propertyType, parentDictionary, columnNames);
2281 if (IsValueType(instance.GetType()))
2283 if (propertyOrField is PropertyInfo tupleProp)
2285 string bfName = $
"<{tupleProp.Name}>k__BackingField";
2286 instance = GetSetStructMethod(instance.GetType()).Invoke(
null,
new[] { instance, bfName, tupleValue });
2288 else if (propertyOrField is FieldInfo tupleField)
2289 instance = GetSetStructMethod(instance.GetType()).Invoke(
null,
new[] { instance, tupleField.Name, tupleValue });
2292 propertyOrField.SetValue(instance, tupleValue);
2295 else if (IsComplexType(propertyType) && propertyValue is IEnumerable && !(propertyValue is
string))
2300 propertyOrField.SetValue(instance, ComplexFromDict(propertyValue));
2304 entryValue = ValueToString(propertyValue, propertyType);
2306 if (
true == IsValueType(instance.GetType()))
2311 if (propertyValue is IDictionary svCycleD && svCycleD.Count == 1 && !IsDictionaryType(propertyType))
2313 foreach (DictionaryEntry svce
in svCycleD)
2315 if (svce.Value is
string svceStr && svceStr.StartsWith(
"Recursion to ["))
2317 propertyValue =
null;
2324 if (propertyOrField is FieldInfo)
2329 if (propertyValue is IDictionary && !IsSimpleType(propertyType))
2330 converted = CreateObjectFromDictionary(propertyValue, propertyType,
null,
null);
2332 converted = ConvertValue(entryValue, propertyType);
2333 instance = GetSetStructMethod(instance.GetType()).Invoke(
null,
new[] { instance, ((FieldInfo)propertyOrField).Name, converted });
2341 if (propertyValue is IDictionary && !IsSimpleType(propertyType))
2342 converted = CreateObjectFromDictionary(propertyValue, propertyType,
null,
null);
2344 converted = ConvertValue(entryValue, propertyType);
2345 string backingName = $
"<{propertyOrField.Name}>k__BackingField";
2346 FieldInfo bf = instance.GetType().GetField(backingName, BindingFlags.NonPublic | BindingFlags.Instance);
2350 instance = GetSetStructMethod(instance.GetType()).Invoke(
null,
new[] { instance, backingName, converted });
2352 propertyOrField.SetValue(instance, converted);
2355 else if (
true == IsVersionType(propertyType))
2357 propertyOrField.SetValue(instance, ConvertValue(propertyValue, propertyType));
2359 else if (IsSimpleType(propertyType) || IsEnumType(propertyType))
2361 if (
null != propertyOrField)
2363 if (typeof(
object) == instance.GetType())
2364 instance = $
"{propertyOrField}{ConverterSettings.OutDelimiter}{propertyValue}";
2369 object convertedValue = ConvertValue(entryValue, propertyType);
2370 propertyOrField.SetValue(instance, convertedValue);
2375 propertyOrField.SetValue(instance, propertyValue);
2379 else if (typeof(
string) == instance?.GetType())
2380 instance = propertyValue;
2382 instance = propertyValue;
2384 else if (
true == IsBigIntegerType(propertyType))
2386 BigInteger bigInteger;
2387 if (
true == BigInteger.TryParse(entryValue, out bigInteger))
2388 propertyOrField.SetValue(instance, bigInteger);
2390 else if (
true == IsComplexType(propertyType))
2392 dynamic complex = ParseComplex(entryValue);
2393 if (
null != complex)
2394 propertyOrField.SetValue(instance, complex);
2396 else if (
true == IsNullableType(propertyType))
2398 Type underlyingType = Nullable.GetUnderlyingType(propertyType);
2399 dynamic underlyingValueObject = InstantiateOne(underlyingType);
2400 SetValue(ref instance, propertyOrField, entryValue, underlyingType, parentDictionary, columnNames);
2402 else if (IsDictionaryType(propertyType))
2404 object value = CreateObjectFromDictionary(propertyValue, propertyType, parentDictionary, columnNames);
2405 propertyOrField.SetValue(instance, value);
2407 else if (
true == IsEnumerableType(propertyType) ||
true == propertyType.IsArray)
2409 dynamic enumeration =
null;
2412 if (
null != propertyValue)
2415 if (propertyValue is
string svNullEnumStr && svNullEnumStr.Trim().Equals(
"null", StringComparison.OrdinalIgnoreCase))
2419 else if ((
true == IsEnumerableObject(propertyValue) && !(propertyValue is
string) && !IsDictionaryType(propertyValue.GetType())) ||
true == IsArray(propertyValue))
2420 enumeration = (IEnumerable)propertyValue;
2423 Type _elemType = propertyType.GetElementType()
2424 ?? (propertyType.IsGenericType ? propertyType.GetGenericArguments()[0] :
null);
2425 bool _compatible = _elemType ==
null
2426 || _elemType.IsAssignableFrom(propertyValue.GetType())
2427 || IsSimpleType(_elemType)
2428 || IsDictionaryType(propertyValue.GetType());
2434 object _singleItem = (_elemType !=
null && propertyValue is IDictionary && !IsDictionaryType(_elemType))
2435 ? CreateObjectFromDictionary(propertyValue, _elemType, parentDictionary, columnNames)
2436 : (object)propertyValue;
2437 enumeration = InstantiateOne(propertyType, 1);
2440 if (
true == IsEnumerableObject(enumeration) &&
false == IsArray(enumeration))
2441 AddToList(enumeration, _singleItem, 0);
2442 else if (
true == IsArray(enumeration))
2443 enumeration[0] = _singleItem;
2447 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"Scalar [{propertyValue?.GetType()}] is not compatible with collection element type [{_elemType}]. Leaving [{propertyType}] at default.", propertyValue, GPALObjectType.Other);
2448 enumeration = InstantiateOne(propertyType, 0);
2452 if (enumeration ==
null)
2453 enumeration = InstantiateOne(propertyType, 0);
2456 foreach (dynamic item
in enumeration)
2459 dynamic list = InstantiateOne(propertyType, count);
2462 foreach (dynamic item
in enumeration)
2464 if (
false == IsCustomStructType(item?.GetType()) &&
null == item)
2466 Type _elemType = propertyType.GetElementType() ?? (propertyType.IsGenericType ? propertyType.GetGenericArguments()[0] :
null);
2467 bool _canBeNull = _elemType ==
null || !_elemType.IsValueType || IsNullableType(_elemType);
2469 AddToList(list,
null, count++);
2473 dynamic itemValue = item;
2474 dynamic itemKey =
null;
2475 Type itemType = item.GetType();
2476 Type objectElementType = propertyType.GetElementType();
2477 if (
null == objectElementType)
2478 objectElementType = propertyType.GetGenericArguments()[0];
2480 if (
true == IsKeyValuePairType(itemType))
2483 itemValue = item.Value;
2484 itemType = item.Value.GetType();
2487 if (
true == IsComplexType(objectElementType))
2489 dynamic complex = ParseComplex(itemValue.ToString());
2490 if (
null != complex)
2491 AddToList(list, complex, count++);
2493 else if (
true == IsSimpleType(objectElementType) || IsEnumType(objectElementType))
2495 dynamic tempVal = ConvertValue(itemValue.ToString(), objectElementType,
null, propertyOrField?.Name);
2496 AddToList(list, tempVal, count++);
2498 else if (IsEnumerableType(objectElementType))
2500 dynamic myList = InstantiateOne(objectElementType);
2501 Type elementType = objectElementType.GenericTypeArguments[0];
2506 dynamic resolvedItemValue = itemValue;
2507 if (resolvedItemValue is IList rilvSingle && rilvSingle.Count == 1 && rilvSingle[0] is IDictionary rilvInner && rilvInner.Count == 1)
2509 foreach (DictionaryEntry rkv
in rilvInner) { resolvedItemValue = rkv.Value is IList ? rkv.Value :
new List<object> { rkv.Value };
break; }
2511 else if (resolvedItemValue is IDictionary rilvDict && rilvDict.Count == 1)
2513 foreach (DictionaryEntry rkv
in rilvDict) { resolvedItemValue = rkv.Value is IList ? rkv.Value :
new List<object> { rkv.Value };
break; }
2518 if (resolvedItemValue !=
null && !(resolvedItemValue is IEnumerable))
2520 resolvedItemValue =
new List<object> { resolvedItemValue };
2523 if (
true == IsComplexType(elementType))
2527 foreach (
object item2
in (IEnumerable<dynamic>)resolvedItemValue)
2529 dynamic iv2 = item2;
2530 if (iv2 is IList il2 && ((IList)il2).Count == 1) iv2 = ((IList)il2)[0];
2531 complex = ParseComplex(iv2?.ToString());
2532 if (
null != complex)
2533 AddToList(myList, complex, innerCount++);
2536 else if (IsSimpleType(elementType) || IsEnumType(elementType) || IsBigIntegerType(elementType))
2538 foreach (
object item2
in (IEnumerable<dynamic>)resolvedItemValue)
2540 dynamic iv2 = item2;
2541 if (iv2 is IList il2 && ((IList)il2).Count == 1) iv2 = ((IList)il2)[0];
2542 dynamic convItem = IsBigIntegerType(elementType)
2543 ? System.Numerics.BigInteger.Parse(iv2?.ToString())
2545 AddToList(myList, convItem, innerCount++);
2548 list[count++] = myList;
2550 else if (itemValue is IDictionary itemDictValue && !IsDictionaryType(objectElementType))
2553 dynamic converted = CreateObjectFromDictionary(itemDictValue, objectElementType, parentDictionary, columnNames);
2554 AddToList(list, converted, count++);
2558 list[count++] = itemValue;
2562 propertyOrField.SetValue(instance, list);
2565 else if (
true == IsClassType(propertyType))
2567 dynamic value =
null;
2568 Dictionary<dynamic, dynamic> myDict =
new Dictionary<dynamic, dynamic>();
2571 if (
null != propertyValue)
2572 foreach (dynamic item
in propertyValue)
2573 if (
true == IsDictionaryType(item.GetType()))
2576 value = ConvertToClass(
new List<Dictionary<dynamic, dynamic>>() { myDict }, propertyType, parentDictionary, columnNames);
2579 myDict[item.Key] = item.Value;
2584 if (
null == value &&
null != propertyValue)
2585 value = ConvertToClass(
new List<Dictionary<dynamic, dynamic>>() { myDict }, propertyType, parentDictionary, columnNames);
2591 IDictionary catchDict = propertyValue as IDictionary;
2592 if (catchDict ==
null && propertyValue is IList pvCatchList)
2594 foreach (var pvItem
in pvCatchList)
2595 if (pvItem is IDictionary d) { catchDict = d;
break; }
2597 if (catchDict !=
null)
2598 value = ConvertToClass(
new List<Dictionary<dynamic, dynamic>>() { (Dictionary<dynamic, dynamic>)(
object)catchDict }, propertyType, parentDictionary, columnNames);
2602 propertyOrField.SetValue(instance, value);
2606 GPAL.
PublishSimpleEvent(GPALEventType.ERROR, $
"Unknown object type [{propertyType}]", propertyValue, GPALObjectType.Other);
2609 private static bool IsSupportedCollectionType(Type type)
2611 return type.IsArray ||
2612 (type.IsGenericType && (
2613 type.GetGenericTypeDefinition() == typeof(List<>) ||
2614 type.GetGenericTypeDefinition() == typeof(IList<>) ||
2615 type.GetGenericTypeDefinition() == typeof(ICollection<>)
2627 internal static bool IsCustomStructType(Type type)
2632 !type.IsValueType ||
2640 if (IsKnownSystemStruct(type))
2647 if (type.Assembly == typeof(
object).Assembly)
2652 if (
true == IsRegisteredStruct(type))
2661 internal static bool IsRegisteredStruct(Type type)
2666 private static bool IsKnownSystemStruct(Type type)
2669 if (type == typeof(decimal) ||
2670 type == typeof(DateTime) ||
2671 type == typeof(DateTimeOffset) ||
2672 type == typeof(TimeSpan) ||
2673 type == typeof(TimeZoneInfo) ||
2674 type == typeof(Guid) ||
2675 type == typeof(BigInteger) ||
2676 type == typeof(Complex))
2682 string ns = type?.Namespace ??
string.Empty;
2683 if (ns.StartsWith(
"System") || ns.StartsWith(
"Microsoft"))
2688 if (ns ==
"System" &&
true == type?.Name.StartsWith(
"ValueTuple"))
2692 if (
true == type?.IsGenericType && type?.GetGenericTypeDefinition() == typeof(Nullable<>))
2696 if (ns ==
"System.Numerics")
2700 if (type?.Name ==
"Half" && ns ==
"System")
2706 internal static bool IsArrayType(Type type)
2708 return (
bool)type?.IsArray;
2711 internal static bool IsClassType(Type type)
2716 return (
bool)type?.IsClass && (bool)!type?.IsInterface;
2718 internal static bool IsUriType(Type type)
2720 return type == typeof(Uri);
2722 internal static bool IsSimpleType(Type type)
2727 if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(KeyValuePair<,>))
2730 return type.IsPrimitive ||
2732 type == typeof(
object) ||
2733 type == typeof(
string) ||
2734 type == typeof(decimal) ||
2735 type == typeof(Uri) ||
2736 type == typeof(System.Net.IPAddress) ||
2737 type == typeof(System.Version) ||
2738 type == typeof(System.Numerics.BigInteger) ||
2739 type == typeof(
bool) ||
2740 type == typeof(Boolean) ||
2741 type == typeof(
byte) ||
2742 type == typeof(sbyte) ||
2743 type == typeof(
char) ||
2744 type == typeof(
short) ||
2745 type == typeof(ushort) ||
2746 type == typeof(
int) ||
2747 type == typeof(uint) ||
2748 type == typeof(
long) ||
2749 type == typeof(ulong) ||
2750 type == typeof(
float) ||
2751 type == typeof(
double) ||
2752 type == typeof(DateTime) ||
2753 type == typeof(DateTimeOffset)
2756 internal static bool IsValueType(Type type)
2758 return type.IsValueType && !type.IsEnum && !type.IsClass && !type.IsInterface;
2762 internal static bool IsDelimitedDataFormat(DataFormat format)
2764 return format == DataFormat.CARET
2765 || format == DataFormat.COLON
2766 || format == DataFormat.CSV
2767 || format == DataFormat.CUSTOM_DELIMITER
2768 || format == DataFormat.DOT
2769 || format == DataFormat.HYPHEN
2770 || format == DataFormat.PIPE
2771 || format == DataFormat.PRN
2772 || format == DataFormat.SEMICOLON
2773 || format == DataFormat.SPACE
2774 || format == DataFormat.TAB;
2780 internal static object ResolveSimpleDictionaryKey(dynamic rawKey, Type keyType)
2782 string rawKeyStr = rawKey as string;
2783 if (rawKeyStr !=
null && rawKeyStr.StartsWith(
"GPALKEY", StringComparison.Ordinal) &&
int.TryParse(rawKeyStr.Substring(7), out
int gpalKeyIdx))
2784 return ConvertValue(gpalKeyIdx, keyType);
2786 if (rawKey !=
null && keyType.IsAssignableFrom(((
object)rawKey).GetType()))
2787 return (
object)rawKey;
2789 return ConvertValue(rawKey?.ToString(), keyType);
2791 internal static bool IsNullableType(Type type)
2793 return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
2795 private static bool IsKeyValuePairType(Type type)
2797 bool retVal =
false;
2800 retVal = ((bool)type?.IsGenericType && type?.GetGenericTypeDefinition() == typeof(KeyValuePair<,>)) || type == typeof(DictionaryEntry);
2806 internal static bool IsDictionaryType(Type type)
2811 return typeof(IDictionary).IsAssignableFrom(type);
2813 public static bool IsBigIntegerType(Type type)
2815 return typeof(BigInteger) == type || typeof(BigInteger?) == type;
2820 private static BigInteger AsBigInteger(
object value)
2822 if (value is BigInteger bi)
return bi;
2823 if (value is
string s)
return BigInteger.Parse(s, NumberStyles.Integer, CultureInfo.InvariantCulture);
2824 return new BigInteger(Convert.ToInt64(value, CultureInfo.InvariantCulture));
2826 public static bool IsByteType(Type type)
2828 return typeof(Byte) == type || typeof(Byte?) == type;
2830 public static bool IsBooleanType(Type type)
2832 return typeof(Boolean) == type || typeof(Boolean?) == type;
2834 public static bool IsDateTimeType(Type type)
2836 return typeof(DateTime) == type || typeof(DateTime?) == type;
2838 public static bool IsDateTimeOffsetType(Type type)
2840 return typeof(DateTimeOffset) == type || typeof(DateTimeOffset?) == type;
2842 public static bool IsIpAddressType(Type type)
2844 return typeof(IPAddress) == type;
2846 public static bool IsTimeSpanType(Type type)
2848 return typeof(TimeSpan) == type || typeof(TimeSpan?) == type;
2850 private static bool IsTupleType(Type type)
2852 if (type ==
null || !type.IsGenericType)
return false;
2853 var openType = type.GetGenericTypeDefinition();
2857 return openType.Namespace ==
"System" &&
2858 (openType.Name.StartsWith(
"ValueTuple`") || openType.Name.StartsWith(
"Tuple`"));
2861 public static bool IsGuidType(Type type)
2863 return typeof(Guid) == type || typeof(Guid?) == type;
2865 public static bool IsVersionType(Type type)
2867 return typeof(System.Version) == type;
2869 public static bool IsEnumerableType(Type type)
2874 return (type.IsArray || typeof(IEnumerable).IsAssignableFrom(type) && type != typeof(
string));
2899 bool foundCycle =
false;
2901 CountGraphNodesRec(root, limit, ref count, ref foundCycle,
new HashSet<object>(ReferenceComparer.Instance));
2903 cyclic = foundCycle;
2919 private static void CountGraphNodesRec(
object node,
int limit, ref
int count, ref
bool cyclic, HashSet<object> onPath)
2921 if (
true == cyclic ||
null == node || (0 < limit && count >= limit))
2924 Type nodeType = node.GetType();
2928 bool tracked =
false == nodeType.IsValueType &&
false == (node is string);
2930 if (
true == tracked &&
false == onPath.Add(node))
2938 if (0 >= limit || count < limit)
2940 if (node is IDictionary dictionary)
2942 foreach (
object value
in dictionary.Values)
2944 CountGraphNodesRec(value, limit, ref count, ref cyclic, onPath);
2946 if (
true == cyclic || (0 < limit && count >= limit))
2950 else if (node is IEnumerable enumerable)
2952 foreach (
object value
in enumerable)
2954 CountGraphNodesRec(value, limit, ref count, ref cyclic, onPath);
2956 if (
true == cyclic || (0 < limit && count >= limit))
2962 foreach (MemberInfo member
in CyclableMembers(nodeType))
2964 CountGraphNodesRec(MemberValue(member, node), limit, ref count, ref cyclic, onPath);
2966 if (
true == cyclic || (0 < limit && count >= limit))
2974 if (
true == tracked)
2975 onPath.Remove(node);
2983 private static MemberInfo[] CyclableMembers(Type type)
2985 return graphMembers.GetOrAdd(type, describing =>
2987 List<MemberInfo> worthWalking =
new List<MemberInfo>();
2989 foreach (PropertyInfo property
in describing.GetProperties(BindingFlags.Public | BindingFlags.Instance))
2990 if (
true == property.CanRead && 0 == property.GetIndexParameters().Length &&
true == CanTakePartInACycle(property.PropertyType))
2991 worthWalking.Add(property);
2993 foreach (FieldInfo field
in describing.GetFields(BindingFlags.Public | BindingFlags.Instance))
2994 if (
true == CanTakePartInACycle(field.FieldType))
2995 worthWalking.Add(field);
2997 return worthWalking.ToArray();
3007 private static bool CanTakePartInACycle(Type type)
3009 return false == (type.IsPrimitive || type.IsEnum || type == typeof(
string) || type == typeof(DateTime)
3010 || type == typeof(DateTimeOffset) || type == typeof(decimal) || type == typeof(Guid)
3011 || type == typeof(TimeSpan) || type == typeof(Uri));
3021 private static object MemberValue(MemberInfo member,
object node)
3023 object retVal =
null;
3027 retVal = member is PropertyInfo
property ?
property.GetValue(node) : ((FieldInfo)member).GetValue(node);
3037 private static readonly System.Collections.Concurrent.ConcurrentDictionary<Type, MemberInfo[]> graphMembers
3038 =
new System.Collections.Concurrent.ConcurrentDictionary<Type, MemberInfo[]>();
3043 private sealed class ReferenceComparer : IEqualityComparer<object>
3045 internal static readonly ReferenceComparer Instance =
new ReferenceComparer();
3047 public new bool Equals(
object left,
object right)
3049 return ReferenceEquals(left, right);
3052 public int GetHashCode(
object of)
3054 return System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(of);
3058 public static bool IsEnumerableObject(dynamic dynamicObject)
3062 if (
null == dynamicObject)
3069 return (typeof(IEnumerable).IsAssignableFrom(dynamicObject?.GetType()) || dynamicObject?.GetType().IsArray) && typeof(
string) != dynamicObject?.GetType();
3071 private static bool IsEnumType(Type type)
3073 Type underlying = Nullable.GetUnderlyingType(type) ?? type;
3074 return underlying.IsEnum;
3076 internal static bool IsComplexType(Type type)
3078 return type == typeof(System.Numerics.Complex);
3080 private static bool IsCompatibleType(Type targetType, Type valueType)
3082 if (valueType ==
null || targetType ==
null)
return false;
3084 if (targetType.IsAssignableFrom(valueType))
return true;
3086 if (IsSimpleType(targetType) && IsSimpleType(valueType))
return true;
3088 if (IsNullableType(targetType))
3090 var underlyingType = Nullable.GetUnderlyingType(targetType);
3091 return IsSimpleType(underlyingType) && IsSimpleType(valueType);
3094 if (IsComplexType(targetType) && valueType == typeof(
string))
return true;
3096 if (IsDictionaryType(targetType) && typeof(IDictionary).IsAssignableFrom(valueType))
return true;
3098 if (IsEnumerableType(targetType) && typeof(IEnumerable).IsAssignableFrom(valueType))
return true;
3100 if (IsClassType(targetType) && typeof(IDictionary).IsAssignableFrom(valueType))
return true;
3104 internal static DataFormat GetDataFormatFromExtension(
string filename)
3106 switch (Path.GetExtension(filename).ToLowerInvariant().Replace(
".",
""))
3109 return DataFormat.CSV;
3111 return DataFormat.TAB;
3114 return DataFormat.JSON;
3119 return DataFormat.LOG;
3124 return DataFormat.PRN;
3127 return DataFormat.XLSX;
3129 return DataFormat.XML;
3132 return DataFormat.YAML;
3135 return DataFormat.HTML;
3137 return DataFormat.NOTSET;
3141 public static void ConvertDictionaryToXmlElement(
3144 bool emitWrapper =
true,
3145 bool isTopLevel =
true,
3146 HashSet<object> visited =
null)
3150 XmlDocument doc = parent.OwnerDocument;
3153 XmlElement currentParent = parent;
3154 if (!emitWrapper && isTopLevel)
3157 currentParent =
null;
3160 Type objType = obj?.GetType();
3161 bool isDictionary = obj is IDictionary;
3162 bool isEnumerable = IsEnumerableType(objType) && !(obj is string);
3163 bool isSimple = IsSimpleType(objType);
3164 bool isTuple = IsTupleType(objType);
3168 if (objType !=
null && !objType.IsValueType && objType != typeof(
string) && (
object)obj !=
null)
3170 if (!visited.Add((
object)obj))
3172 if (currentParent !=
null)
3174 var bt = objType.Name.IndexOf(
'`');
3175 var cycleName = bt >= 0 ? objType.Name.Substring(0, bt) : objType.Name;
3176 currentParent.InnerText = $
"Recursion to [{cycleName}]";
3186 foreach (var kvp
in dict)
3188 string key = kvp.Key.ToString();
3189 if (key.StartsWith(
"@GPAL_"))
3192 if (currentParent !=
null)
3193 currentParent.SetAttribute(key.Substring(6), ValueToString(kvp.Value));
3196 foreach (var kvp
in dict)
3198 string key = kvp.Key.ToString();
3199 if (key ==
"#text" && currentParent !=
null)
3201 currentParent.InnerText = ValueToString(kvp.Value);
3210 foreach (var kvp
in dict)
3212 string key = kvp.Key.ToString();
3213 if (key.StartsWith(
"@") || key ==
"#text")
continue;
3214 if (key.StartsWith(
"GPALKEY")) key =
"row";
3218 else if (System.Text.RegularExpressions.Regex.IsMatch(key,
@"^GPAL\d+_"))
3219 key = System.Text.RegularExpressions.Regex.Replace(key,
@"^GPAL\d+_",
"");
3221 string elementName = SanitizeXmlElementName(key);
3222 dynamic value = kvp.Value;
3224 XmlElement child = doc.CreateElement(elementName);
3227 if (currentParent ==
null)
3229 currentParent = child;
3230 doc.AppendChild(child);
3234 currentParent.AppendChild(child);
3237 ConvertDictionaryToXmlElement(value, child, emitWrapper,
false, visited);
3240 else if (isEnumerable)
3242 foreach (dynamic item
in obj)
3246 XmlElement child = doc.CreateElement(
"item");
3247 currentParent.AppendChild(child);
3248 ConvertDictionaryToXmlElement(item, child, emitWrapper,
false, visited);
3252 ConvertDictionaryToXmlElement(item, currentParent, emitWrapper,
false, visited);
3258 if (currentParent !=
null)
3260 var fields = objType.GetFields();
3262 foreach (var field
in fields)
3264 dynamic fieldValue = field.GetValue(obj);
3265 XmlElement child = doc.CreateElement(SanitizeXmlElementName(field.Name));
3266 currentParent.AppendChild(child);
3267 ConvertDictionaryToXmlElement(fieldValue, child, emitWrapper:
true, isTopLevel:
false, visited: visited);
3271 else if (isSimple && (objType ==
null || !objType.IsValueType || objType.IsPrimitive || objType.IsEnum
3272 || objType == typeof(DateTime) || objType == typeof(DateTimeOffset) || objType == typeof(decimal)
3273 || IsNullableType(objType) || IsGuidType(objType) || IsTimeSpanType(objType) || IsBigIntegerType(objType)
3274 || IsComplexType(objType)))
3276 if (currentParent !=
null)
3277 currentParent.InnerText = ValueToString(obj);
3279 else if ((
object)obj !=
null && currentParent !=
null)
3282 foreach (var prop
in objType.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance))
3286 dynamic val = prop.GetValue(obj);
3287 XmlElement child = doc.CreateElement(SanitizeXmlElementName(prop.Name));
3288 currentParent.AppendChild(child);
3289 ConvertDictionaryToXmlElement(val, child, emitWrapper,
false, visited);
3293 foreach (var field
in objType.GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance))
3297 dynamic val = field.GetValue(obj);
3298 XmlElement child = doc.CreateElement(SanitizeXmlElementName(field.Name));
3299 currentParent.AppendChild(child);
3300 ConvertDictionaryToXmlElement(val, child, emitWrapper,
false, visited);
3306 public static void ConvertHtmlDictionaryToXml(dynamic obj, XmlElement parent)
3311 XmlDocument doc = parent.OwnerDocument;
3314 if (obj is IEnumerable enumerable && !(obj is
string) && !(obj is IDictionary))
3316 foreach (dynamic child
in enumerable)
3318 ConvertHtmlDictionaryToXml(child, parent);
3324 if (obj is
string text)
3326 parent.AppendChild(doc.CreateTextNode(text));
3331 if (
true == IsDictionaryType(obj.GetType()))
3334 if (obj.TryGetValue(
"tag", out tagObj) &&
"#comment".Equals(tagObj?.ToString()))
3336 string commentText =
"";
3338 if (obj.TryGetValue(
"value", out valueObj))
3340 commentText = SanitizeComment(ValueToString(valueObj));
3344 parent.AppendChild(doc.CreateComment(commentText));
3350 if (!(obj is IDictionary elementDict))
3352 parent.AppendChild(doc.CreateTextNode(ValueToString(obj)));
3356 string tagName =
"div";
3357 dynamic attributes =
null;
3358 dynamic children =
null;
3359 string textContent =
null;
3361 foreach (DictionaryEntry entry
in elementDict)
3363 string key = entry.Key.ToString().ToLower();
3365 tagName = entry.Value?.ToString() ??
"div";
3366 else if (key ==
"attributes")
3367 attributes = entry.Value;
3368 else if (key ==
"children")
3369 children = entry.Value;
3370 else if (key ==
"text")
3371 textContent = entry.Value?.ToString();
3374 XmlElement element = doc.CreateElement(SanitizeXmlElementName(tagName));
3377 if (attributes is IDictionary attrDict)
3379 foreach (DictionaryEntry attr
in attrDict)
3381 string attrName = attr.Key.ToString();
3382 string attrValue = ValueToString(attr.Value);
3383 element.SetAttribute(attrName, attrValue);
3388 if (!
string.IsNullOrEmpty(textContent))
3390 element.AppendChild(doc.CreateTextNode(textContent));
3394 if (children !=
null)
3396 ConvertHtmlDictionaryToXml(children, element);
3399 parent.AppendChild(element);
3403 private static string ValueToString(dynamic value, Type type =
null)
3406 type = value?.GetType();
3409 if (IsKeyValuePairType(type))
3410 return ValueToString(value.Value);
3412 if (
false == IsCustomStructType(type))
3415 if (
null == value || value is
char c && c ==
'\0')
3425 if (value is
string alreadyString)
3426 return alreadyString;
3431 if (value is IList scalarList && type !=
null && !IsEnumerableType(type) && !type.IsArray)
3433 object first = scalarList.Count > 0 ? scalarList[0] :
null;
3434 if (first ==
null)
return "null";
3435 if (first is
string fs)
return fs;
3437 if (first is IDictionary fd)
3439 foreach (DictionaryEntry de
in fd)
3440 if (de.Value is
string dvs)
return dvs;
3444 if (first is IList)
return ValueToString(first, type);
3445 return first.ToString();
3448 if (IsTupleType(type))
3450 var fields = type.GetFields();
3451 var parts =
new List<string>();
3452 foreach (var field
in fields)
3454 var fieldValue = field.GetValue(value);
3455 parts.Add(ValueToString(fieldValue));
3457 return $
"({string.Join(",
", parts)})";
3461 if (IsComplexType(type))
3463 double im = value.Imaginary;
3464 string sign = im >= 0 ?
"+" :
"-";
3465 return $
"{value.Real}{sign}{Math.Abs(im)}i";
3469 if (IsEnumType(type))
3474 Type enumType = Nullable.GetUnderlyingType(type) ?? type;
3475 object realValue = value;
3478 if (value is
string strValue)
3481 var tryParseMethod = typeof(Enum).GetMethod(nameof(Enum.TryParse),
new[]
3485 enumType.MakeByRefType()
3488 if (tryParseMethod !=
null)
3490 var parameters =
new object[] { strValue,
true,
null };
3491 bool success = (bool)tryParseMethod.Invoke(
null, parameters);
3495 realValue = parameters[2];
3505 dynamic enumObject = InstantiateOne(enumType);
3507 return enumObject.ToString();
3520 long numeric = Convert.ToInt64(realValue);
3527 string name = Enum.GetName(enumType, Enum.ToObject(enumType, numeric));
3532 if (enumType.IsDefined(typeof(FlagsAttribute), inherit:
false))
3534 var names = Enum.GetValues(enumType)
3536 .Where(v => Convert.ToInt64(v) != 0 && (numeric & Convert.ToInt64(v)) == Convert.ToInt64(v))
3537 .Select(v => Enum.GetName(enumType, v))
3538 .Where(n => n !=
null);
3541 return string.Join(
", ", names);
3545 return numeric.ToString(CultureInfo.InvariantCulture);
3549 if (IsDateTimeType(type))
3551 return value.ToString(
"MM/dd/yyyy h:mm:ss tt");
3555 if (IsGuidType(type))
3557 return value.ToString();
3561 if (IsUriType(type))
3563 return value.ToString();
3567 if (IsIpAddressType(type))
3569 return value.ToString();
3573 if (IsTimeSpanType(type))
3575 return value.ToString(
"c");
3579 if (IsVersionType(type))
3581 return value.ToString();
3585 if (IsBigIntegerType(type))
3587 return Convert.ToString(value, CultureInfo.InvariantCulture);
3591 if (type == typeof(
float) || type == typeof(
double))
3593 if (
double.IsNaN((
double)value))
3595 if (
double.IsPositiveInfinity((
double)value))
3597 if (
double.IsNegativeInfinity((
double)value))
3600 return Convert.ToString(value, CultureInfo.InvariantCulture);
3603 if (typeof(WaitTime) == type)
3606 if (
true == TryConvertToWaitTime(value, out wt))
3607 return wt.ToString();
3609 wt = CreateObjectFromDictionary(value, type,
null,
null);
3610 return wt.ToString();
3614 if (IsSimpleType(type))
3616 return Convert.ToString(value, CultureInfo.InvariantCulture);
3619 var toString = GetToString(type);
3621 return (
string)toString.Invoke(value,
null);
3623 private static MethodInfo GetToString(Type t)
3625 if (!ToStringCache.TryGetValue(t, out var method))
3627 method = t.GetMethod(
3629 BindingFlags.Instance | BindingFlags.Public,
3631 types: Type.EmptyTypes,
3634 ToStringCache[t] = method;
3640 private static string SanitizeComment(
string text)
3642 if (
string.IsNullOrEmpty(text))
3645 text = text.Replace(
"<!--",
"").Replace(
"-->",
"").Replace(
"--",
"-");
3648 if (text.EndsWith(
"-"))
3654 private static string SanitizeXmlElementName(
string key)
3656 if (
string.IsNullOrEmpty(key))
3659 if (key.StartsWith(
"GPALKEY", StringComparison.Ordinal))
3660 key = key.Substring(7);
3661 else if (System.Text.RegularExpressions.Regex.IsMatch(key,
@"^GPAL\d+_"))
3662 key = System.Text.RegularExpressions.Regex.Replace(key,
@"^GPAL\d+_",
"");
3665 string sanitized = (key.Length > 0 && !
char.IsLetter(key[0]) && key[0] !=
'_')
3670 sanitized = sanitized.Replace(
":",
"_");
3673 sanitized = Regex.Replace(sanitized,
"[^a-zA-Z0-9_\\-.]",
"_");
3676 if (
string.IsNullOrEmpty(sanitized))
3681 internal static string ConvertInputDictionaryToDelimitedAndGrid(
ConverterSettings converterSettings, out IGPALGrid<string> outGrid)
3683 StringBuilder sb =
new StringBuilder();
3684 StringBuilder sb2 =
new StringBuilder();
3685 IGPALGrid<string> outputGrid =
GPAL.
Grid.ToGPALObject();
3686 List<string> gridRow =
new List<string>();
3687 List<Dictionary<object, dynamic>> rows =
new List<Dictionary<object, dynamic>>();
3688 List<string> flattenLastMessage =
new List<string>();
3689 bool flattenSupressedMessage =
false;
3693 IEnumerable<object> rootItems = cleanedInput as IEnumerable<object> ??
new[] { cleanedInput };
3700 bool LooksLikeRecordList(dynamic value) =>
3701 value is IDictionary asDict && asDict.Count > 0 &&
3702 asDict.Keys.Cast<
object>().All(k =>
int.TryParse(k?.ToString(), out _));
3704 void EmitRows(dynamic value)
3706 if (LooksLikeRecordList(value))
3708 foreach (dynamic subValue
in ((IDictionary)value).Values)
3715 if (
false == IsCustomStructType(value?.GetType()) && (value ==
null || (value is ICollection emptyCheck && emptyCheck.Count == 0)))
3718 flattenLastMessage.Clear();
3719 flattenSupressedMessage =
false;
3720 var row =
new Dictionary<object, dynamic>();
3726 if (
false == IsCustomStructType(value?.GetType()) &&
null != value &&
true == IsSimpleType(value.GetType()))
3727 row[
"0"] = value.ToString();
3729 Flatten(value, ref row);
3734 foreach (dynamic item
in rootItems)
3739 List<string> headers =
null;
3743 bool useUserSuppliedHeaders = 0 < ((IGPALFileInternal)converterSettings.OutputFile)?.FileSettings.ColumnList.Count() && 0 < ((IGPALFileInternal)converterSettings.OutputFile)?.FileSettings.ColumnList[0].Count();
3747 List<string> canonicalFieldNames =
null;
3750 canonicalFieldNames =
new List<string>();
3751 foreach (var row
in rows)
3754 foreach (
string key
in row.Keys.Cast<
string>())
3756 int existingIndex = canonicalFieldNames.IndexOf(key);
3757 if (existingIndex >= 0)
3759 lastIndex = existingIndex;
3763 canonicalFieldNames.Insert(lastIndex + 1, key);
3769 if (useUserSuppliedHeaders)
3770 headers = ((IGPALFileInternal)converterSettings.OutputFile).FileSettings.ColumnList[0].ToList();
3772 headers = canonicalFieldNames;
3774 if (
true == converterSettings.FirstLineIsColumnHeaders &&
null != headers)
3776 foreach (var header
in headers)
3778 sb2.Append(CreateCSVToken(header.ToString(), converterSettings));
3779 gridRow.Add(header.ToString());
3781 if (sb2.Length > 0) sb2.Length--;
3782 sb.AppendLine(sb2.ToString());
3784 outputGrid.AddRow(gridRow);
3788 int headerCount = headers?.Count ?? 0;
3790 for (
int rowIndex = startIndex; rowIndex < rows.Count; rowIndex++)
3792 var dict = rows[rowIndex];
3793 gridRow =
new List<string>();
3795 for (
int i = 0; i < headerCount; i++)
3797 string key = useUserSuppliedHeaders ? (i < canonicalFieldNames.Count ? canonicalFieldNames[i] :
null) : headers[i];
3798 string value = key !=
null && dict.ContainsKey(key) && dict[key] !=
null ? dict[key].ToString() :
"";
3799 sb2.Append(CreateCSVToken(value, converterSettings));
3802 char delim =
true == converterSettings.OutDelimiter.HasValue ? converterSettings.OutDelimiter.Value :
',';
3804 if (sb2.Length > 0 && sb2[sb2.Length - 1] == delim) sb2.Length--;
3805 sb.AppendLine(sb2.ToString());
3807 outputGrid.AddRow(gridRow);
3810 void Flatten(dynamic dictionaryOrList, ref Dictionary<object, dynamic> outDict,
string prefix =
"")
3812 StringBuilder sb3 =
new StringBuilder();
3813 Dictionary<object, dynamic> outDict2 =
new Dictionary<object, dynamic>();
3814 Type dictOrListType = dictionaryOrList?.GetType();
3816 if (
true == IsDictionaryType(dictOrListType))
3818 foreach (dynamic kvp
in dictionaryOrList)
3820 Type kvpType = kvp.GetType();
3821 dynamic kvpValue = kvp;
3822 dynamic kvpKey =
null;
3824 if (
true == IsKeyValuePairType(kvpType))
3826 kvpValue = kvp.Value;
3830 Type kvpValueType = kvpValue?.GetType();
3832 if (
true == IsComplexType(kvpValueType))
3834 Complex complex = (Complex)kvpValue;
3836 sb3.Append($
"{CreateCSVToken(complex.Real.ToString(), converterSettings)}{CreateCSVToken(complex.Imaginary.ToString(), converterSettings)}");
3838 sb3.Remove(sb3.Length - 1, 1);
3840 outDict[prefix + kvpKey] = sb3.ToString();
3843 else if (
true == IsSimpleType(kvpValueType))
3845 if (outDict.TryGetValue(prefix + kvpKey, out dynamic val))
3846 sb3.Append($
"{val}");
3848 sb3.Append(ValueToString(kvpValue));
3850 outDict[prefix + kvpKey] = sb3.ToString();
3853 else if (
true == IsIpAddressType(kvpValueType))
3855 IPAddress iPAddress = (IPAddress)kvpValue;
3856 sb3.Append(iPAddress.ToString());
3858 outDict[prefix + kvpKey] = sb3.ToString();
3861 else if (IsBigIntegerType(kvpValueType) || IsVersionType(kvpValueType))
3863 sb3.Append(kvpValue.ToString());
3865 outDict[prefix + kvpKey] = sb3.ToString();
3868 else if (
true == IsDictionaryType(kvpValueType))
3870 Flatten(kvpValue, ref outDict2, prefix + kvpKey +
".");
3871 foreach (var item
in outDict2)
3873 outDict[item.Key] = item.Value;
3877 else if ((
true == IsEnumerableType(kvpValueType) ||
true == kvpValueType.IsArray))
3880 foreach (var item
in kvpValue)
3882 Flatten(item, ref outDict2, $
"{prefix}{kvpKey}{index++}.");
3883 foreach (var item2
in outDict2)
3885 outDict[item2.Key] = item2.Value;
3890 else if ((
true == IsClassType(kvpValueType)) && kvpValueType != typeof(
string))
3892 foreach (var prop
in kvpValueType.GetProperties(BindingFlags.Public | BindingFlags.Instance))
3896 object propValue = prop.GetValue(kvpValue);
3897 if (propValue !=
null)
3899 Flatten(propValue, ref outDict2, $
"{prefix}{kvpKey}.{prop.Name}.");
3900 foreach (var item
in outDict2)
3902 outDict[item.Key] = item.Value;
3906 else if (
true == IsSimpleType(prop.PropertyType))
3912 outDict[$
"{prefix}{kvpKey}.{prop.Name}."] =
"";
3915 catch (Exception ex)
3917 GPAL.
PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Failed to flatten property [{prop.Name}] of [{kvpValueType.Name}]", kvp, GPALObjectType.Other, ex);
3923 GPAL.
PublishSimpleEvent(GPALEventType.DEBUG, $
"Unable to handle type [{kvpValueType}]", kvp, GPALObjectType.Other);
3929 if (
true == IsKeyValuePairType(dictOrListType))
3931 if (
true == IsDictionaryType(dictionaryOrList.Value.GetType()))
3933 Flatten(dictionaryOrList.Value, ref outDict2, $
"{prefix}");
3934 foreach (var item2
in outDict2)
3936 outDict[item2.Key] = item2.Value.Trim(
true == converterSettings.InDelimiter.HasValue ? converterSettings.InDelimiter.Value :
',');
3941 outDict[dictionaryOrList.Key] = dictionaryOrList.Value;
3944 else if (
false == IsCustomStructType(dictOrListType) &&
true == ValueToString(dictionaryOrList).Equals(
"null"))
3947 outDict[prefix] =
null;
3949 else if (
true == IsComplexType(dictOrListType))
3951 Complex complex = (Complex)dictionaryOrList;
3953 sb3.Append($
"{complex.Real.ToString()}{complex.Imaginary.ToString()}");
3955 sb3.Remove(sb3.Length - 1, 1);
3957 outDict[prefix] = sb3.ToString();
3959 else if (
true == IsSimpleType(dictOrListType))
3961 if (outDict.TryGetValue(prefix, out dynamic val))
3962 sb3.Append($
"{val}");
3964 sb3.Append(dictionaryOrList.ToString());
3966 outDict[prefix] = sb3.ToString();
3968 else if (
true == IsIpAddressType(dictOrListType))
3970 IPAddress iPAddress = (IPAddress)dictionaryOrList;
3971 sb3.Append(iPAddress.ToString());
3973 outDict[prefix] = sb3.ToString();
3975 else if (IsBigIntegerType(dictOrListType) || IsVersionType(dictOrListType))
3977 sb3.Append(dictionaryOrList.ToString());
3979 outDict[prefix] = sb3.ToString();
3981 else if (
true == IsEnumerableType(dictOrListType) ||
true == IsArrayType(dictOrListType))
3984 foreach (var item
in dictionaryOrList)
3986 Flatten(item, ref outDict2, $
"{prefix}{index++}");
3987 foreach (var item2
in outDict2)
3989 outDict[item2.Key] = item2.Value;
3996 string flattenMsg = $
"Unable to handle type [{dictOrListType}]";
3997 if (
false == flattenLastMessage.Contains(flattenMsg))
3999 flattenLastMessage.Add(flattenMsg);
4001 flattenSupressedMessage =
false;
4003 else if (
false == flattenSupressedMessage)
4005 GPAL.
PublishSimpleEvent(GPALEventType.INFO,
"Supressing repeat messages", dictionaryOrList, GPALObjectType.Other);
4006 flattenSupressedMessage =
true;
4012 outGrid = outputGrid;
4013 return sb.ToString();
4015 internal static string CreateCSVToken(
string inputString,
ConverterSettings converterSettings)
4017 char delimiter =
true == converterSettings.OutDelimiter.HasValue ? converterSettings.OutDelimiter.Value :
',';
4018 inputString = (inputString ??
"").TrimEnd(delimiter);
4020 StringBuilder sb =
new StringBuilder();
4021 if (
true == converterSettings.FieldsEnclosedInQuotes)
4024 sb.Append(inputString);
4026 if (
true == converterSettings.FieldsEnclosedInQuotes)
4029 if (0 == sb.Length || delimiter != sb[sb.Length - 1])
4030 sb.Append(delimiter);
4032 return sb.ToString();
4034 internal static Dictionary<object, dynamic> ConvertXMLToDictionary(XmlDocument xmlDoc)
4037 var rootDict =
new Dictionary<object, dynamic>();
4040 string rootName = xmlDoc.DocumentElement.Name;
4043 dynamic contentDict = ConvertXmlNodeToDictionary(xmlDoc.DocumentElement);
4046 rootDict[rootName] = contentDict;
4051 private static int xmlItemCount = 0;
4052 private static Dictionary<object, dynamic> ConvertXmlNodeToDictionary(XmlNode xmlNode)
4054 var dict =
new Dictionary<object, dynamic>();
4059 if (xmlNode.Attributes !=
null)
4061 foreach (XmlAttribute attr
in xmlNode.Attributes)
4063 dict[
"@GPAL_" + attr.Name] = attr.Value;
4068 var childGroups =
new Dictionary<string, List<Dictionary<object, dynamic>>>();
4070 string textContent =
null;
4071 string rawWhitespaceText =
null;
4073 foreach (XmlNode childNode
in xmlNode.ChildNodes)
4075 if (childNode.NodeType == XmlNodeType.Element)
4077 var childDict = ConvertXmlNodeToDictionary(childNode);
4078 string name = $
"GPAL{xmlItemCount++.ToString("D3
")}_"+childNode.Name;
4080 if (!childGroups.TryGetValue(name, out var list))
4082 list =
new List<Dictionary<object, dynamic>>();
4083 childGroups[name] = list;
4085 list.Add(childDict);
4087 else if (childNode.NodeType == XmlNodeType.Text
4088 || childNode.NodeType == XmlNodeType.Whitespace
4089 || childNode.NodeType == XmlNodeType.SignificantWhitespace)
4091 string raw = childNode.Value ??
string.Empty;
4092 string trimmed = raw.Trim();
4093 if (!
string.IsNullOrEmpty(trimmed))
4094 textContent = trimmed;
4095 else if (raw.Length > 0)
4096 rawWhitespaceText = raw;
4101 if (textContent ==
null && rawWhitespaceText !=
null && childGroups.Count == 0)
4102 textContent = rawWhitespaceText;
4105 foreach (var group
in childGroups)
4110 dict[group.Key] = group.Value;
4114 if (textContent !=
null)
4116 dict[
"#text"] = textContent;
4119 catch (Exception ex)
4121 GPAL.
PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Unable to convert XMLnode [{xmlNode}] to dictionary.",
null, GPALObjectType.None, ex);
4126 internal static List<Dictionary<object, dynamic>> ConvertGridToDictionary(List<string> columnNames, IGPALGrid<string> rows,
int startRow = 0,
int rowCnt = -1)
4128 List<Dictionary<object, dynamic>> returnList =
new List<Dictionary<object, dynamic>>();
4129 var rowDictionary =
new Dictionary<object, dynamic>();
4133 if (0 == startRow && -1 == rowCnt)
4136 foreach (List<string> row
in rows)
4138 rowDictionary =
new Dictionary<object, dynamic>();
4141 foreach (
string columnName
in columnNames)
4142 rowDictionary[columnName] = row[idx++];
4144 returnList.Add(rowDictionary);
4147 catch (Exception ex)
4149 GPAL.
PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Unable to convert grid to dictionary.", rows, GPALObjectType.Other, ex);
4155 for (
int rowIdx = startRow; ourRowCnt < rowCnt; ourRowCnt++)
4157 rowDictionary =
new Dictionary<object, dynamic>();
4158 List<string> row = rows[rowIdx++];
4161 foreach (
string columnName
in columnNames)
4162 rowDictionary[columnName] = row[idx++];
4164 returnList.Add(rowDictionary);
4167 catch (Exception ex)
4169 GPAL.
PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Unable to convert grid to dictionary.", rows, GPALObjectType.Other, ex);
4188 internal static Dictionary<object, dynamic> ConvertDelimitedToDictionary(
4191 string filename =
null)
4193 var records =
new Dictionary<object, dynamic>();
4194 List<string> headers =
null;
4198 converterSettings?.InDelimiter ??
4199 GetDelimiterFromFormat(
4200 GetDataFormatFromExtension(filename ??
@"csv")
4207 if (inputFile is Stream stream)
4209 stream.Position = 0;
4210 reader =
new StreamReader(stream);
4212 else if (inputFile is StreamReader sr)
4216 else if (inputFile is StringReader str)
4223 GPALEventType.ERROR,
4224 $@"Unsupported inputFile type [{inputFile?.GetType().FullName}]",
4226 GPALObjectType.GPALFile);
4231 while ((line = reader.ReadLine()) !=
null)
4233 if (
string.IsNullOrWhiteSpace(line))
4239 var tokens = SplitRespectingQuotes(line, delimiter);
4242 if (lineNumber == 0)
4244 if (converterSettings?.FirstLineIsColumnHeaders ==
true)
4254 if (headers !=
null)
4257 var rowDict =
new Dictionary<string, string>();
4258 for (
int i = 0; i < Math.Min(headers.Count, tokens.Count); i++)
4260 rowDict[headers[i]] = tokens[i];
4262 records[$
"GPALKEY{lineNumber:D4}"] = rowDict;
4267 records[$
"GPALKEY{lineNumber:D4}"] = tokens;
4273 catch (Exception ex)
4276 GPALEventType.EXCEPTION,
4277 $@"Exception while parsing delimited file",
4279 GPALObjectType.GPALFile, ex);
4282 if (records.Count == 0)
4286 $@"No data found in input file [{filename}]",
4288 GPALObjectType.GPALFile);
4300 private static IEnumerable<string> ReadAllLines(TextReader reader)
4302 var lines =
new List<string>();
4306 while (
null != (line = reader.ReadLine()))
4313 internal static char? GetDelimiterFromFormat(DataFormat dataFormat)
4317 case DataFormat.CARET:
4319 case DataFormat.COLON:
4321 case DataFormat.CSV:
4323 case DataFormat.DOT:
4325 case DataFormat.HYPHEN:
4327 case DataFormat.PIPE:
4329 case DataFormat.PRN:
4330 case DataFormat.SPACE:
4332 case DataFormat.SEMICOLON:
4334 case DataFormat.TAB:
4337 case DataFormat.HTML:
4338 case DataFormat.JSON:
4340 case DataFormat.XLSX:
4341 case DataFormat.XML:
4342 case DataFormat.YAML:
4345 case DataFormat.CUSTOM_DELIMITER:
4355 internal static bool IsArrayLike(dynamic dictListOrObject)
4357 Type parmType = dictListOrObject.GetType();
4359 if (IsDictionaryType(parmType) || IsEnumerableType(parmType))
4361 if (dictListOrObject ==
null || dictListOrObject.Count == 0)
return false;
4363 foreach (dynamic entry
in dictListOrObject)
4367 string keyStr = entry.Key.ToString();
4368 if (!keyStr.StartsWith(
"GPALKEY"))
return false;
4369 if (
int.TryParse(keyStr.Substring(7), out
int idx))
4387 private static bool TryGetIdAndRest(IDictionary dict, out
string id, out Dictionary<object, dynamic> rest)
4390 rest =
new Dictionary<object, dynamic>();
4392 if (dict.Contains(
"id"))
4394 dynamic idValue = dict[
"id"];
4395 if (idValue is
string idStr)
4398 foreach (DictionaryEntry kvp
in dict)
4400 if (!
"id".Equals(kvp.Key.ToString()))
4402 rest[kvp.Key] = kvp.Value;
4410 foreach (DictionaryEntry kvp
in dict)
4412 rest[kvp.Key] = kvp.Value;
4418 private static string RenderFullObjectToHtml(IDictionary dict,
bool emitUl =
false,
bool isTopLevel =
true,
string listStyle =
"style='margin: 8px; padding-left: 8px;'")
4420 var sb =
new StringBuilder();
4421 int dictCount = dict.Count;
4423 if (
true == isTopLevel)
4424 sb.Append($
"<ul id='renderfull' {listStyle}>\n");
4426 foreach (
object keyObj
in dict.Keys)
4428 string key = keyObj.ToString().Replace(
"@GPAL_",
"");
4430 key = System.Text.RegularExpressions.Regex.Replace(key,
@"^GPAL\d+_",
"");
4435 if (
true == System.Text.RegularExpressions.Regex.IsMatch(key,
@"^GPALKEY\d+$"))
4438 if (
"#comment".Equals(key))
continue;
4439 dynamic value = dict[keyObj];
4441 if (
false == key.Equals(
"#text") &&
false ==
string.IsNullOrEmpty(key))
4443 sb.Append(
"<li><strong>");
4444 sb.Append(HttpUtility.HtmlEncode(key));
4445 sb.Append(
"</strong>: ");
4446 listStyle =
"style = 'list-style: none; margin: 8px; padding-left: 8px;'";
4448 Type valueType = value?.GetType();
4449 bool isCollection = IsDictionaryType(valueType) || IsEnumerableType(valueType);
4450 bool isTuple = IsTupleType(valueType);
4452 int collectionCount = 0;
4454 if (
true == isCollection)
4455 collectionCount = ObjectCopier.GetItemCount(
null, value);
4457 if (
true == emitUl && 1 < dictCount)
4458 sb.Append($
"<ul id=rf2 {listStyle}><li>\n");
4460 sb.Append(RenderValue(value, isCollection || isTuple, isCollection || isTuple ?
"style='margin: 8px; padding-left: 8px;'" : listStyle));
4462 if (
true == emitUl && 1 < dictCount)
4463 sb.Append(
"</ul></li>");
4465 if (
false == key.Equals(
"#text"))
4466 sb.Append(
"</li>\n");
4469 if (
true == isTopLevel)
4470 sb.Append(
"</ul>\n");
4472 return sb.ToString();
4475 private static int idCnt = 0;
4478 private static string RenderValue(dynamic value,
bool renderUl =
false,
string ulStyle =
"style='margin: 0px; padding-left: 0px;", HashSet<object> visited =
null)
4480 var valueSb =
new StringBuilder();
4481 Type valueType = value?.GetType();
4482 string vtn = valueType?.Name;
4485 bool isRootCall = visited ==
null;
4486 bool isNull =
false;
4495 isNull =
null == value;
4502 Dictionary<object, dynamic> dict =
new Dictionary<object, dynamic>();
4504 if (
false == isNull)
4508 if (valueType.IsClass || valueType.IsInterface)
4510 if (visited.TryGetValue(value, out dynamic fuggetAboutIt))
4512 string referenceName =
string.Empty;
4516 referenceName = value.Value;
4522 referenceName = value.Name;
4526 referenceName = valueType.Name;
4530 return $
"Recursion to [{referenceName}]";
4538 if (
true == IsKeyValuePairType(valueType))
4540 return $
"<strong>{value.Key}</strong>: {RenderValue(value.Value, true, ulStyle, visited)}";
4543 else if (IsTupleType(valueType))
4545 if (
true == renderUl)
4546 valueSb.Append($
"<ul id=rvtuple{idCnt++} {ulStyle}'>\n");
4549 var fields = valueType.GetFields();
4550 var parts =
new List<string>();
4551 foreach (dynamic field
in fields)
4553 var fieldValue = field.GetValue(value);
4554 valueSb.AppendLine($
"<li><strong>{field.Name}</strong>: {RenderValue(fieldValue, true, ulStyle, visited)}</li>");
4557 if (
true == renderUl)
4558 valueSb.Append(
"</ul>\n");
4560 else if (
false == IsCustomStructType(valueType) && value ==
null)
4561 return HttpUtility.HtmlEncode(
"null");
4563 else if (value is Dictionary<object, dynamic> nestedDict)
4565 valueSb.Append(RenderFullObjectToHtml(nestedDict, renderUl,
false));
4568 else if (
true == IsEnumerableObject(value) && !(value is
string))
4570 if (
true == renderUl)
4571 valueSb.Append($
"<ul id=rvenum{idCnt++} {ulStyle}'>\n");
4573 foreach (var item
in value)
4575 Type itemType = item?.GetType();
4576 bool isCollection = IsDictionaryType(itemType) || IsEnumerableType(itemType);
4577 string toRender = RenderValue(item, isCollection || renderUl, ulStyle, visited);
4579 if (
false ==
string.IsNullOrEmpty(toRender))
4583 if ((
false == isCollection ||
true == renderUl) &&
false == toRender.StartsWith(
"<li>"))
4584 valueSb.Append(
"<li>");
4586 valueSb.Append(toRender);
4588 if ((
false == isCollection ||
true == renderUl) &&
false == toRender.StartsWith(
"<li>"))
4589 valueSb.Append(
"</li>\n");
4593 if (
true == renderUl)
4594 valueSb.Append(
"</ul>\n");
4598 else if (
true == IsSimpleType(valueType) &&
false == IsCustomStructType(valueType))
4600 string toRender = ValueToString(value);
4601 if (
false ==
string.IsNullOrEmpty(toRender))
4602 valueSb.Append($
"{HttpUtility.HtmlEncode(toRender)}");
4604 else if (
true == IsCustomStructType(valueType) ||
true == IsClassType(valueType))
4606 if (
true == renderUl)
4607 valueSb.Append($
"<ul id=rvstruct{idCnt++} {ulStyle}'>\n");
4609 var fields = valueType.GetFields(BindingFlags.Public | BindingFlags.Instance);
4610 var props = valueType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
4612 foreach (var field
in fields)
4614 dynamic fieldValue = field?.GetValue(value);
4615 valueSb.Append($
"<li><strong>{field?.Name}</strong>: ");
4616 Type fieldValueType = fieldValue?.GetType();
4618 if (
true == IsEnumerableType(fieldValueType))
4619 valueSb.Append(RenderValue(fieldValue,
true, ulStyle, visited));
4621 valueSb.Append(ValueToString(fieldValue));
4623 valueSb.Append(
"</li>\n");
4626 foreach (var prop
in props)
4628 if (
true == prop?.CanRead && prop?.GetIndexParameters().Length == 0)
4630 dynamic propValue = prop?.GetValue(value);
4631 valueSb.Append($
"<li><strong>{prop?.Name}</strong>: ");
4632 Type propValueType = propValue?.GetType();
4633 bool isEnumerable = IsEnumerableType(propValueType);
4634 bool isClass = IsClassType(propValueType);
4635 bool isSimple = IsSimpleType(propValueType);
4637 if (
true == isEnumerable || (
true == isClass &&
false == isSimple))
4638 valueSb.Append(RenderValue(propValue,
true, ulStyle, visited));
4640 valueSb.Append(ValueToString(propValue));
4642 valueSb.Append(
"</li>\n");
4646 if (
true == renderUl)
4647 valueSb.Append(
"</ul>\n");
4651 valueSb.Append(ValueToString(value));
4652 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"Render [{valueType}] using ValueToString", value, GPALObjectType.Other);
4655 return valueSb.ToString();
4659 public static string ConvertDictionaryToHtml(Dictionary<object, dynamic> dictionary)
4661 var sb =
new StringBuilder();
4663 if (0 == dictionary.Count)
4665 GPAL.
PublishSimpleEvent(GPALEventType.WARNING,
"Dictionary is empty. No html will be output.", dictionary, GPALObjectType.Converter);
4666 return string.Empty;
4669 if (dictionary.ContainsKey(
"tag"))
4671 var tagName = dictionary[
"tag"].ToString();
4672 if (
"#comment".Equals(tagName))
4674 if (dictionary.ContainsKey(
"value"))
4676 sb.Append($
" {dictionary["value
"].ToString()}\n");
4678 return sb.ToString();
4681 if (
false ==
"#text".Equals(tagName))
4683 sb.Append($
"<{tagName}");
4684 if (dictionary.ContainsKey(
"attributes"))
4686 dynamic attributes = dictionary[
"attributes"];
4687 foreach (var attribute
in attributes)
4689 sb.Append($
" {attribute.Key}=\"{attribute.Value}\"");
4714 if (dictionary.ContainsKey(
"text"))
4716 sb.Append(dictionary[
"text"].ToString());
4719 if (dictionary.ContainsKey(
"children"))
4721 var children = (List<Dictionary<object, dynamic>>)dictionary[
"children"];
4722 foreach (var child
in children)
4724 if (
false == child.ContainsKey(
"#text"))
4726 string text = ConvertDictionaryToHtml(child);
4727 if (0 < text.Length)
4731 sb.Append(child[
"text"]);
4746 sb.Append($
"</{tagName}>\n");
4753 bool isArray = IsArrayLike(dictionary);
4756 sb.Append($
"<ul id=list style='margin: 8px; padding-left: 8px;'>\n");
4758 foreach (var kvp
in dictionary)
4760 dynamic item = kvp.Value;
4761 string toRender = RenderValue(item,
true,
"style='margin: 8px; padding-left: 8px;");
4763 if (
false ==
string.IsNullOrEmpty(toRender))
4766 sb.Append(toRender);
4767 sb.Append(
"</li>\n");
4775 sb.Append(RenderFullObjectToHtml(dictionary));
4778 return sb.ToString();
4789 var dictionaries =
new List<Dictionary<object, dynamic>>();
4790 HtmlNodeCollection nodes =
null;
4792 if (inputDataOrFilename is HtmlNodeCollection)
4793 nodes = inputDataOrFilename;
4795 nodes = GetHtmlNodes(inputDataOrFilename);
4799 GPAL.
PublishSimpleEvent(GPALEventType.WARNING,
"No html nodes found. Dictionary will be empty", inputDataOrFilename, GPALObjectType.Converter);
4800 return new List<Dictionary<object, dynamic>>();
4803 foreach (var node
in nodes)
4806 if (node.Name ==
"#document" &&
false == isChildren)
4810 if (
true ==
"#comment".Equals(node.Name))
4812 if (
false == isChildren)
4815 var commentDict =
new Dictionary<object, dynamic>
4817 {
"tag",
"#comment" },
4818 {
"value", node.InnerHtml }
4820 dictionaries.Add(commentDict);
4825 if (
true ==
"#text".Equals(node.Name))
4827 if (
false == isChildren)
4830 var textDict =
new Dictionary<object, dynamic>
4833 {
"value", node.InnerHtml }
4835 dictionaries.Add(textDict);
4839 var dict =
new Dictionary<object, dynamic>();
4842 dict[
"tag"] = node.Name;
4843 if (node.Attributes.Count > 0)
4845 var attributes =
new Dictionary<string, string>();
4846 foreach (var attribute
in node.Attributes)
4848 attributes[attribute.Name] = attribute.Value;
4850 dict[
"attributes"] = attributes;
4854 if (!
string.IsNullOrEmpty(node.InnerText.Trim()) && !node.HasChildNodes)
4856 dict[
"text"] = node.InnerText.Trim();
4860 if (node.HasChildNodes)
4862 var children =
new List<Dictionary<object, dynamic>>();
4863 foreach (var childNode
in node.ChildNodes)
4865 if (childNode.Name ==
"#text")
4867 if (!
string.IsNullOrEmpty(childNode.InnerHtml.Trim()))
4870 if (node.ChildNodes.All(n => n.Name ==
"#text"))
4872 dict[
"text"] = childNode.InnerHtml.Trim();
4877 children.Add(
new Dictionary<object, dynamic>
4880 {
"value", childNode.InnerHtml }
4888 if (childDictionaries.Count > 0)
4890 foreach (var childDict
in childDictionaries)
4892 children.Add(childDict);
4897 if (children.Count > 0)
4898 dict[
"children"] = children;
4901 dictionaries.Add(dict);
4903 return dictionaries;
4905 public static HtmlNodeCollection GetHtmlNodes(dynamic htmlorFilename)
4908 var doc =
new HtmlAgilityPack.HtmlDocument();
4911 htmlString = File.ReadAllText(htmlorFilename);
4915 htmlString = htmlorFilename;
4920 doc.LoadHtml(htmlString);
4921 return doc.DocumentNode.ChildNodes;
4923 catch (Exception ex)
4925 GPAL.
PublishSimpleEvent(GPALEventType.ERROR, $
"Expected html string or GPALFile, got [{htmlorFilename.GetType()}]", htmlorFilename, GPALObjectType.Other, ex);
4930 public static dynamic CreateIEnumerableFromClass<T>(
ConverterSettings converterSettings)
4932 IEnumerable<T> resultList =
null;
4933 bool isArrayOutput = converterSettings.OutputClassType.IsArray;
4934 T[] arrayOutput =
null;
4935 int arrayCapacity = 0;
4936 int elementIndex = 0;
4941 arrayCapacity = ((Array)Activator.CreateInstance(converterSettings.OutputClassType, 4)).Length;
4945 arrayCapacity = converterSettings.InputDictionary.Count();
4951 arrayOutput = (T[])Array.CreateInstance(converterSettings.OutputClassType.GetElementType(), arrayCapacity);
4953 for (
int i = 0; i < arrayCapacity; i++)
4955 arrayOutput[i] = (T)InstantiateOne(converterSettings.OutputClassElementType);
4960 switch (converterSettings.OutputClassType)
4963 resultList = (IEnumerable<T>)ActivatorHelper.CreateInstance(converterSettings.OutputClassType);
4970 foreach (var dict
in converterSettings.InputDictionary)
4972 if (isArrayOutput && inputIndex >= arrayCapacity)
4977 bool allKeysSame =
false;
4980 allKeysSame = CheckKeysPrefix(dict);
4982 catch (Exception ex)
4984 GPAL.
PublishSimpleEvent(GPALEventType.EXCEPTION, $
"CheckKeysPrefix exception", converterSettings, GPALObjectType.Other, ex);
4988 int dictCount = ObjectCopier.GetItemCount(
null, dict);
4990 foreach (var dict2
in dict)
4992 T element = (T)(
object)InstantiateOne(converterSettings.OutputClassElementType);
4994 dynamic retVal =
null;
4995 if (
true == allKeysSame)
4996 retVal = ConvertToClass(dict, converterSettings.OutputClassType, dict,
null);
4998 retVal = ConvertToClass(dict2.Value, converterSettings.OutputClassElementType, dict,
null);
5002 int count = ObjectCopier.GetItemCount(
null, retVal);
5007 foreach (dynamic element2
in retVal)
5009 arrayOutput[elementIndex++] = (T)element2;
5012 arrayOutput[elementIndex++] = (T)retVal;
5014 catch (IndexOutOfRangeException)
5016 GPAL.
PublishSimpleEvent(GPALEventType.INFO, $
"[{converterSettings.OutputClassType.Name}] does not have enough capacity for [{dictCount}] items. Returning [{elementIndex - 1}]", retVal, GPALObjectType.Other);
5020 else if (converterSettings.OutputClassType == retVal.GetType())
5024 element = (T)retVal;
5031 BindingFlags.Public | BindingFlags.Instance,
null,
new[] { ConverterSettings.OutputClassElementType },
null);
5033 BindingFlags.Public | BindingFlags.Instance,
null,
new[] { typeof(List<>).MakeGenericType(ConverterSettings.OutputClassElementType) },
null);
5034 MethodInfo enqueueMethod =
ConverterSettings.OutputClassType.GetMethod(
"Enqueue",
5035 BindingFlags.Public | BindingFlags.Instance,
null,
new[] { ConverterSettings.OutputClassElementType },
null);
5037 BindingFlags.Public | BindingFlags.Instance,
null,
new[] { ConverterSettings.OutputClassElementType },
null);
5039 if (
null != addRowMethod)
5041 converterSettings.OutputData = converterSettings.InputData[dictIdx];
5042 if (
null != converterSettings.InDelimiter &&
null != converterSettings.OutDelimiter)
5043 converterSettings.OutputData = converterSettings.InputData[dictIdx].Replace(converterSettings.InDelimiter.Value, converterSettings.OutDelimiter.Value);
5044 string[] lines = converterSettings.OutputData.Split(
new[] {
'\r',
'\n' }, StringSplitOptions.RemoveEmptyEntries);
5046 foreach (
object line
in lines)
5048 addRowMethod.Invoke(resultList,
new object[] {
new List<object>() { (dynamic)line } });
5051 else if (
null != addMethod)
5052 addMethod.Invoke(resultList,
new object[] { element });
5053 else if (
null != enqueueMethod)
5054 enqueueMethod.Invoke(resultList,
new object[] { element });
5055 else if (
null != pushMethod)
5056 pushMethod.Invoke(resultList,
new object[] { element });
5058 GPAL.
PublishSimpleEvent(GPALEventType.ERROR, $
"Can't find 'Add/Enqueue/Push' method for [{converterSettings.OutputClassType}]", converterSettings, GPALObjectType.Other);
5061 catch (Exception ex)
5063 GPAL.
PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Can't 'Add' to [{converterSettings.OutputClassType}]", converterSettings, GPALObjectType.Other, ex);
5066 if (
false == allKeysSame)
5074 return isArrayOutput ? arrayOutput : resultList;
5076 public static List<T> CreateListFromClass<T>(
ConverterSettings converterSettings)
5078 List<T> resultList =
new List<T>();
5080 foreach (var data
in converterSettings.InputDictionary)
5082 dynamic element = InstantiateOne(converterSettings.OutputClassElementType);
5083 element = ConvertToClass(
new List<Dictionary<object, dynamic>>() { data }, converterSettings.OutputClassElementType, converterSettings.InputDictionary,
null);
5085 resultList.Add(element);
5089 public static dynamic InstantiateOne(Type objectType,
int arrayLength = 0)
5091 if (objectType ==
null)
5093 GPAL.
PublishSimpleEvent(GPALEventType.ERROR,
"ObjectType is null in InstantiateOne",
null, GPALObjectType.Other);
5097 if (objectType.IsArray)
5099 Type elementType = objectType.GetElementType();
5100 Array dynamicArray = Array.CreateInstance(elementType, arrayLength);
5101 for (
int i = 0; i < arrayLength; i++)
5103 dynamicArray.SetValue(InstantiateOne(elementType), i);
5105 return dynamicArray;
5107 else if (objectType == typeof(
string))
5109 return string.Empty;
5111 else if (objectType.IsValueType)
5113 return ActivatorHelper.CreateInstance(objectType);
5115 else if (IsAnonymousType(objectType))
5117 return new System.Dynamic.ExpandoObject();
5124 return ActivatorHelper.CreateInstance(objectType);
5126 catch (Exception exInner)
5132 Type[] genericArgs = objectType.IsGenericType
5133 ? objectType.GetGenericArguments()
5134 :
new[] { typeof(
object), typeof(
object) };
5136 Type keyType = genericArgs[0];
5137 Type valueType = genericArgs.Length > 1 ? genericArgs[1] : typeof(
object);
5139 Type concreteDictType = typeof(Dictionary<,>).MakeGenericType(keyType, valueType);
5140 return ActivatorHelper.CreateInstance(concreteDictType);
5142 catch (Exception ex)
5145 $
"Failed to instantiate dictionary type [{objectType}] (fallback also failed: [{exInner.Message}])",
5146 null, GPALObjectType.Other, ex);
5147 return new System.Dynamic.ExpandoObject();
5156 return ActivatorHelper.CreateInstance(objectType);
5158 catch (MissingMethodException)
5161 var constructors = objectType.GetConstructors(BindingFlags.Public | BindingFlags.Instance);
5162 foreach (var ctor
in constructors)
5164 var parameters = ctor.GetParameters();
5165 var args =
new object[parameters.Length];
5166 for (
int i = 0; i < parameters.Length; i++)
5168 var paramType = parameters[i].ParameterType;
5169 if (paramType == typeof(
string))
5170 args[i] =
string.Empty;
5171 else if (paramType == typeof(
int))
5173 else if (paramType == typeof(Dictionary<string, string>))
5174 args[i] =
new Dictionary<string, string>();
5175 else if (paramType.IsValueType)
5176 args[i] = ActivatorHelper.CreateInstance(paramType);
5182 return ctor.Invoke(args);
5189 GPAL.
PublishSimpleEvent(GPALEventType.ERROR, $
"No suitable constructor for type [{objectType}]",
null, GPALObjectType.Other);
5196 private static bool IsAnonymousType(Type type)
5198 return Attribute.IsDefined(type, typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute),
false) &&
5199 type.IsGenericType &&
5200 type.Name.Contains(
"Anonymous") &&
5201 (type.Attributes & TypeAttributes.NotPublic) == TypeAttributes.NotPublic;
5204 private static Complex ComplexFromDict(dynamic value)
5207 dynamic dict = value;
5208 if (value is IEnumerable && !(value is IDictionary) && !(value is
string))
5209 foreach (var item
in (IEnumerable)value) { dict = item;
break; }
5211 double real = 0, imaginary = 0;
5212 if (dict is IDictionary idict)
5214 foreach (DictionaryEntry entry
in idict)
5216 string rawEntryKey = entry.Key?.ToString() ??
string.Empty;
5217 string key = rawEntryKey.StartsWith(
"GPALKEY", StringComparison.Ordinal)
5218 ? rawEntryKey.Substring(7)
5219 : (System.Text.RegularExpressions.Regex.IsMatch(rawEntryKey,
@"^GPAL\d+_")
5220 ? System.Text.RegularExpressions.Regex.Replace(rawEntryKey,
@"^GPAL\d+_",
"")
5224 object val = entry.Value;
5225 if (val is IEnumerable valEnum && !(val is
string))
5226 foreach (var v
in valEnum) { val = v;
break; }
5228 string str = val?.ToString();
5229 if (key.Equals(
"Real", StringComparison.OrdinalIgnoreCase))
5230 double.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out real);
5231 else if (key.Equals(
"Imaginary", StringComparison.OrdinalIgnoreCase))
5232 double.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out imaginary);
5235 return new Complex(real, imaginary);
5238 internal static dynamic ParseComplex(
string complexString)
5240 string s = complexString.Trim();
5242 string numPart = s.Substring(0, s.Length - 1);
5244 int signIndex = Math.Max(numPart.LastIndexOf(
'+'), numPart.LastIndexOf(
'-'));
5245 if (signIndex == -1)
throw new FormatException(
"No sign found");
5247 string realStr = numPart.Substring(0, signIndex);
5248 string imagStr = numPart.Substring(signIndex);
5250 double real =
string.IsNullOrEmpty(realStr) ? 0 :
double.Parse(realStr, CultureInfo.InvariantCulture);
5251 double imag =
double.Parse(imagStr, CultureInfo.InvariantCulture);
5253 return new Complex(real, imag);
5256 public static bool AddToList(dynamic listQueueStackDictHashsetOrArray, dynamic value,
int count)
5259 value = EnsureCollectionType(listQueueStackDictHashsetOrArray, value);
5261 if ((
true == IsEnumerableObject(listQueueStackDictHashsetOrArray) &&
false == IsArray(listQueueStackDictHashsetOrArray)) ||
true == IsList(listQueueStackDictHashsetOrArray))
5266 MethodInfo addMethod = listQueueStackDictHashsetOrArray.GetType().GetMethod(
"Add",
5267 BindingFlags.Public | BindingFlags.Instance);
5268 MethodInfo enqueueMethod = listQueueStackDictHashsetOrArray.GetType().GetMethod(
"Enqueue",
5269 BindingFlags.Public | BindingFlags.Instance);
5270 MethodInfo pushMethod = listQueueStackDictHashsetOrArray.GetType().GetMethod(
"Push",
5271 BindingFlags.Public | BindingFlags.Instance);
5273 if (
null != addMethod)
5275 if (
true ==
ConverterHelper.IsDictionaryType(listQueueStackDictHashsetOrArray.GetType()))
5276 addMethod.Invoke(listQueueStackDictHashsetOrArray,
new object[] { count, value });
5278 addMethod.Invoke(listQueueStackDictHashsetOrArray,
new object[] { value });
5280 else if (
null != enqueueMethod)
5281 enqueueMethod.Invoke(listQueueStackDictHashsetOrArray,
new object[] { value });
5282 else if (
null != pushMethod)
5283 pushMethod.Invoke(listQueueStackDictHashsetOrArray,
new object[] { value });
5285 catch (Exception ex)
5287 GPAL.
PublishSimpleEvent(GPALEventType.DEBUG, $
"Can't Add [{value}] to [{listQueueStackDictHashsetOrArray}]. May require a Convert.ChangeType", listQueueStackDictHashsetOrArray, GPALObjectType.Other, ex);
5292 else if (
true == IsArray(listQueueStackDictHashsetOrArray))
5293 listQueueStackDictHashsetOrArray[count] = EnsureCollectionType(listQueueStackDictHashsetOrArray, value);
5306 private static object EnsureCollectionType(
object container,
object value)
5308 if (container ==
null || value ==
null)
return value;
5309 Type containerType = container.GetType();
5312 Type targetElementType = containerType.IsArray ? containerType.GetElementType() : (containerType.IsGenericType ? containerType.GetGenericArguments()[0] :
null);
5316 if (targetElementType ==
null || targetElementType.IsAssignableFrom(value.GetType()))
5320 object convertedItem = ConvertValue(value, targetElementType, container);
5323 return WrapInCollection(targetElementType, convertedItem);
5346 private static object WrapInCollection(Type typeToCreate, dynamic item)
5349 if (typeToCreate.IsArray)
5351 Array newArray = Array.CreateInstance(typeToCreate.GetElementType(), 1);
5352 newArray.SetValue(item, 0);
5356 if (typeToCreate == item.GetType())
5360 object collection = Activator.CreateInstance(typeToCreate);
5363 MethodInfo addMethod = typeToCreate.GetMethod(
"Add")
5364 ?? typeToCreate.GetMethod(
"Push")
5365 ?? typeToCreate.GetMethod(
"Enqueue");
5367 addMethod?.Invoke(collection,
new[] { item });
5371 internal static string UppercaseFirst(
string input)
5373 if (
string.IsNullOrEmpty(input))
5376 return char.ToUpper(input[0]) + input.Substring(1);
5379 static bool CheckKeysPrefix(Dictionary<dynamic, dynamic> dict)
5381 if (!dict.Any())
return true;
5383 string commonPrefix =
null;
5385 foreach (var entry
in dict.Values)
5391 IEnumerable<string> innerKeys;
5397 var innerDict = (IDictionary<string, object>)entry;
5398 innerKeys = innerDict.Keys;
5406 if (!innerKeys.Any())
5409 string currentKey = innerKeys.First();
5411 string prefix = GetPrefixUpToNumber(currentKey);
5413 if (
string.IsNullOrEmpty(prefix))
5416 if (commonPrefix ==
null)
5418 commonPrefix = prefix;
5420 else if (currentKey != prefix && !currentKey.StartsWith(commonPrefix))
5429 static string GetPrefixUpToNumber(
string key)
5431 if (
string.IsNullOrEmpty(key))
return string.Empty;
5434 while (i < key.Length && !
char.IsDigit(key[i]))
5437 return i > 0 ? key.Substring(0, i) :
string.Empty;
5439 public static Type GetConcreteType(Type type)
5441 if (type ==
null)
throw new ArgumentNullException(nameof(type));
5444 if (!type.IsInterface && !type.IsAbstract)
5450 return type.Assembly.GetTypes()
5451 .FirstOrDefault(t => !t.IsInterface && !t.IsAbstract && type.IsAssignableFrom(t))
5452 ??
throw new InvalidOperationException($
"No concrete implementation found for {type.Name}.");
5454 private static object ConvertValue(dynamic value, Type targetType, dynamic parentDictionary =
null,
string propertyName =
null)
5456 string fromCtx = propertyName !=
null ? $
" from [{propertyName}]" :
string.Empty;
5459 bool isNullInput =
false;
5461 if (targetType == typeof(
object))
5465 if (IsTupleType(value?.GetType()) && IsTupleType(targetType))
5467 Type targetTupleType = Nullable.GetUnderlyingType(targetType) ?? targetType;
5470 if (value is System.Runtime.CompilerServices.ITuple sourceTuple)
5472 int arity = sourceTuple.Length;
5473 Type[] targetElementTypes = targetTupleType.GetGenericArguments();
5476 if (arity == targetElementTypes.Length)
5478 var convertedItems =
new object[arity];
5479 for (
int i = 0; i < arity; i++)
5481 object item = sourceTuple[i];
5482 convertedItems[i] = ConvertValue(item, targetElementTypes[i]);
5487 var ctor = targetTupleType.GetConstructor(targetElementTypes);
5490 return ctor.Invoke(convertedItems);
5498 bool isStruct = IsCustomStructType(value?.GetType());
5500 Type ut2 = Nullable.GetUnderlyingType(targetType) ?? targetType;
5501 bool isCharTarget = ut2 == typeof(
char);
5502 isNullInput =
false == isStruct
5504 (value is
string str && !isCharTarget &&
string.IsNullOrWhiteSpace(str)) ||
5505 (value is
string s2 && s2.Trim().Equals(
"null", StringComparison.OrdinalIgnoreCase))
5515 if (IsNullableType(targetType) || targetType.IsClass)
5519 $
"Cannot convert [{DescribeForLog(value)}] to non-nullable [{ShortTypeName(targetType)}]{fromCtx}. Returning default.",
5520 null, GPALObjectType.Other);
5521 return Activator.CreateInstance(targetType);
5524 Type underlyingTargetType = Nullable.GetUnderlyingType(targetType) ?? targetType;
5527 if (IsSupportedCollectionType(underlyingTargetType))
5529 Type elementType = underlyingTargetType.IsArray
5530 ? underlyingTargetType.GetElementType()!
5531 : underlyingTargetType.GetGenericArguments()[0];
5533 IEnumerable<object> sourceItems = (value is IEnumerable enumerable && !(value is string))
5534 ? enumerable.Cast<
object>()
5537 var convertedItems =
new List<object>();
5539 foreach (var item
in sourceItems)
5541 object converted = ConvertValue(item, elementType);
5542 convertedItems.Add(converted);
5546 return CreateCollection(convertedItems, underlyingTargetType);
5552 if (value is
string floatStr)
5554 string trimmed = floatStr.Trim().ToLowerInvariant();
5555 if (underlyingTargetType == typeof(
float) || underlyingTargetType == typeof(
double))
5557 if (trimmed ==
"nan" || trimmed ==
".nan")
5558 return underlyingTargetType == typeof(
float) ? (object)
float.NaN : double.NaN;
5559 if (trimmed ==
"infinity" || trimmed ==
".inf" || trimmed ==
"+infinity" || trimmed ==
"+.inf")
5560 return underlyingTargetType == typeof(
float) ? (object)
float.PositiveInfinity : double.PositiveInfinity;
5561 if (trimmed ==
"-infinity" || trimmed ==
"-.inf")
5562 return underlyingTargetType == typeof(
float) ? (object)
float.NegativeInfinity : double.NegativeInfinity;
5567 if (IsEnumType(underlyingTargetType))
5569 string strValue = value.ToString().Trim();
5572 var tryParseMethod = typeof(Enum)
5573 .GetMethods(BindingFlags.Public | BindingFlags.Static)
5574 .FirstOrDefault(m =>
5575 m.Name == nameof(Enum.TryParse)
5576 && m.IsGenericMethodDefinition
5577 && m.GetGenericArguments().Length == 1
5578 && m.GetParameters().Length == 3
5579 && m.GetParameters()[0].ParameterType == typeof(
string)
5580 && m.GetParameters()[1].ParameterType == typeof(
bool)
5581 && m.GetParameters()[2].IsOut);
5583 if (tryParseMethod !=
null)
5585 var genericMethod = tryParseMethod.MakeGenericMethod(underlyingTargetType);
5586 object[] parameters =
new object[] { strValue,
true,
null };
5587 bool success = (bool)genericMethod.Invoke(
null, parameters);
5590 return parameters[2];
5595 if (
long.TryParse(strValue, out
long numericValue))
5599 return Enum.ToObject(underlyingTargetType, numericValue);
5609 $
"Invalid enum value [{value}] for type [{ShortTypeName(underlyingTargetType)}]{fromCtx}. Returning default (0).",
5610 null, GPALObjectType.Other);
5612 return Enum.ToObject(underlyingTargetType, 0);
5616 if (value is
string strValue2)
5618 string trimmed = strValue2.Trim();
5620 if (IsGuidType(underlyingTargetType))
5622 if (Guid.TryParse(trimmed, out Guid guid))
5625 if (trimmed.Length > 1 && trimmed[0] ==
'_' && Guid.TryParse(trimmed.Substring(1), out Guid strippedGuid))
5626 return strippedGuid;
5627 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"Invalid Guid format: [{strValue2}]{fromCtx}. Returning Guid.Empty.",
null, GPALObjectType.Other);
5631 if (IsIpAddressType(underlyingTargetType))
5633 if (IPAddress.TryParse(trimmed, out IPAddress ip))
5635 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"Invalid IPAddress format: [{strValue2}]{fromCtx}. Returning IPAddress.Any.",
null, GPALObjectType.Other);
5636 return IPAddress.Any;
5639 if (IsUriType(underlyingTargetType))
5641 if (Uri.TryCreate(trimmed, UriKind.RelativeOrAbsolute, out Uri uri))
5643 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"Invalid Uri format: [{strValue2}]{fromCtx}. Returning null.",
null, GPALObjectType.Other);
5647 if (IsTimeSpanType(underlyingTargetType))
5649 if (TimeSpan.TryParse(trimmed, CultureInfo.InvariantCulture, out TimeSpan ts))
5651 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"Invalid TimeSpan format: [{strValue2}]{fromCtx}. Returning TimeSpan.Zero.",
null, GPALObjectType.Other);
5652 return TimeSpan.Zero;
5658 if (IsDateTimeType(underlyingTargetType)
5659 && DateTime.TryParse(trimmed, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out DateTime roundTripped))
5660 return roundTripped;
5664 if (IsDateTimeOffsetType(underlyingTargetType))
5666 if (DateTimeOffset.TryParse(trimmed, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out DateTimeOffset dto))
5668 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"Invalid DateTimeOffset format: [{strValue2}]{fromCtx}. Returning DateTimeOffset.MinValue.",
null, GPALObjectType.Other);
5669 return DateTimeOffset.MinValue;
5672 if (IsBigIntegerType(underlyingTargetType))
5674 if (BigInteger.TryParse(trimmed, NumberStyles.Integer, CultureInfo.InvariantCulture, out BigInteger bi))
5676 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"Invalid BigInteger format: [{strValue2}]{fromCtx}. Returning 0.",
null, GPALObjectType.Other);
5677 return BigInteger.Zero;
5680 if (IsComplexType(underlyingTargetType))
5684 return ParseComplex(trimmed);
5688 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"Invalid Complex format: [{strValue2}]{fromCtx}. Returning 0+0i.",
null, GPALObjectType.Other);
5689 return new Complex(0, 0);
5693 if (IsVersionType(underlyingTargetType))
5695 if (System.Version.TryParse(trimmed, out System.Version ver))
5697 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"Invalid Version format: [{strValue2}]{fromCtx}. Returning 0.0.",
null, GPALObjectType.Other);
5698 return new System.Version(0, 0);
5703 if (
true == IsVersionType(underlyingTargetType))
5705 if (value.GetType() == typeof(System.Version))
5708 List<int> versionInfo =
new List<int>();
5711 foreach (KeyValuePair<object, object> item
in value)
5715 versionInfo.Add(Int32.Parse(item.Value.ToString()));
5718 return new System.Version(versionInfo[0], versionInfo[1], versionInfo[2], versionInfo[3]);
5721 if (typeof(WaitTime) == underlyingTargetType)
5724 if (
true == TryConvertToWaitTime(value, out wt))
5728 if (
true == IsCustomStructType(underlyingTargetType))
5732 if (value.GetType() == underlyingTargetType)
5735 return ConvertToClass(value, underlyingTargetType, parentDictionary,
null);
5737 catch (Exception ex)
5740 $
"Failed to convert [{value}] [{ShortTypeName(value?.GetType())}] to [{ShortTypeName(underlyingTargetType)}]{fromCtx}. Returning default.",
5741 null, GPALObjectType.Other, ex);
5742 return Activator.CreateInstance(underlyingTargetType);
5747 if (IsBigIntegerType(underlyingTargetType))
5749 try {
return AsBigInteger(value); }
5752 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"Invalid BigInteger value: [{value}]{fromCtx}. Returning 0.",
null, GPALObjectType.Other);
5753 return BigInteger.Zero;
5758 if (IsSimpleType(underlyingTargetType))
5762 if (value is BigInteger bigIntSrc)
5764 try {
return System.Convert.ChangeType(bigIntSrc.ToString(CultureInfo.InvariantCulture), underlyingTargetType, CultureInfo.InvariantCulture); }
5765 catch (Exception ex)
5767 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"Failed to convert BigInteger [{value}] to [{ShortTypeName(underlyingTargetType)}]{fromCtx}. Returning default.",
null, GPALObjectType.Other, ex);
5768 return Activator.CreateInstance(underlyingTargetType);
5773 return System.Convert.ChangeType(value, underlyingTargetType, CultureInfo.InvariantCulture);
5775 catch (Exception ex)
5778 $
"Failed to convert [{value}] [{ShortTypeName(value?.GetType())}] to [{ShortTypeName(underlyingTargetType)}]{fromCtx}. Returning default.",
5779 null, GPALObjectType.Other, ex);
5780 return Activator.CreateInstance(underlyingTargetType);
5785 if (targetType.IsAssignableFrom(value.GetType()))
5790 if (value is
string cvCycleStr && cvCycleStr.StartsWith(
"Recursion to [") && cvCycleStr.EndsWith(
"]") && targetType != typeof(
string))
5791 return targetType.IsValueType ? Activator.CreateInstance(targetType) :
null;
5794 if (value is IDictionary dictSrc && IsClassType(targetType) && !IsDictionaryType(targetType))
5796 try {
return CreateObjectFromDictionary(dictSrc, targetType, dictSrc,
null); }
5797 catch (Exception ex)
5799 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"Unable to reconstruct [{ShortTypeName(targetType)}] from dict{fromCtx}", value, GPALObjectType.None, ex);
5804 if (value is IList cvListVal && IsClassType(targetType) && !IsDictionaryType(targetType) && !IsSupportedCollectionType(targetType))
5806 object cvInner = cvListVal;
5807 while (cvInner is IList cvInnerList && cvInnerList.Count == 1)
5808 cvInner = cvInnerList[0];
5809 if (cvInner is IDictionary cvInnerDict)
5811 try {
return CreateObjectFromDictionary(cvInnerDict, targetType, cvInnerDict,
null); }
5812 catch (Exception ex)
5814 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"Unable to reconstruct [{ShortTypeName(targetType)}] from unwrapped dict{fromCtx}", value, GPALObjectType.None, ex);
5821 $
"Unable to convert [{DescribeForLog(value)}] to [{ShortTypeName(targetType)}]{fromCtx}",
5822 null, GPALObjectType.Other);
5826 private static string ShortTypeName(Type t)
5828 if (t ==
null)
return "null";
5829 if (IsNullableType(t))
5830 return ShortTypeName(Nullable.GetUnderlyingType(t)) +
"?";
5831 if (!t.IsGenericType)
return t.Name;
5833 return "(" +
string.Join(
", ", t.GetGenericArguments().Select(a => ShortTypeName(a))) +
")";
5834 string baseName = t.Name.Substring(0, t.Name.IndexOf(
'`'));
5835 string args =
string.Join(
", ", t.GetGenericArguments().Select(a => ShortTypeName(a)));
5836 return $
"{baseName}<{args}>";
5839 private static string CompactValue(
object v,
int depth = 0)
5841 if (v ==
null)
return "null";
5842 if (v is
string s)
return s.Length <= 40 ? s : s.Substring(0, 40) +
"...";
5845 if (v is IDictionary)
return "{...}";
5846 if (v is IEnumerable)
return "[...]";
5848 if (v is IDictionary dict)
5850 var pairs =
new List<string>();
5851 foreach (dynamic kvp
in dict) { pairs.Add($
"{kvp.Key}={CompactValue(kvp.Value, depth + 1)}");
if (pairs.Count >= 3)
break; }
5852 return "{" +
string.Join(
", ", pairs) + (dict.Count > 3 ?
", ..." :
"") +
"}";
5854 if (v is IEnumerable seq)
5856 var items =
new List<string>();
5857 foreach (var item
in seq) { items.Add(CompactValue(item, depth + 1));
if (items.Count >= 2)
break; }
5858 return "[" +
string.Join(
", ", items) + (items.Count >= 2 ?
", ..." :
"") +
"]";
5862 string str = v.ToString();
5863 return str == v.GetType().FullName ? ShortTypeName(v.GetType()) : str;
5865 catch {
return ShortTypeName(v.GetType()); }
5868 private static string DescribeForLog(
object value,
int maxLen = 80)
5870 if (value ==
null)
return "null";
5871 if (value is
string s)
5872 return s.Length <= maxLen ? s : s.Substring(0, maxLen) +
"...";
5873 if (value is IDictionary dict)
5875 var pairs =
new List<string>();
5876 foreach (dynamic kvp
in dict) { pairs.Add($
"{kvp.Key}={CompactValue(kvp.Value)}");
if (pairs.Count >= 3)
break; }
5877 return "{" +
string.Join(
", ", pairs) + (dict.Count > 3 ?
", ..." :
"") +
"}";
5879 if (value is IEnumerable seq)
5881 var items =
new List<string>();
5882 foreach (var item
in seq) { items.Add(CompactValue(item));
if (items.Count >= 3)
break; }
5883 return "[" +
string.Join(
", ", items) + (items.Count >= 3 ?
", ..." :
"") +
"]";
5887 string str = value.ToString();
5888 return str == value.GetType().FullName ? ShortTypeName(value.GetType()) : (str.Length <= maxLen ? str : str.Substring(0, maxLen) +
"...");
5890 catch {
return ShortTypeName(value.GetType()); }
5893 private static object CreateCollection(List<object> convertedItems, Type targetCollectionType)
5895 Type underlying = targetCollectionType;
5896 if (targetCollectionType.IsGenericType &&
5897 targetCollectionType.GetGenericTypeDefinition() == typeof(Nullable<>))
5899 underlying = Nullable.GetUnderlyingType(targetCollectionType)!;
5902 if (underlying.IsArray)
5904 Type elementType = underlying.GetElementType()!;
5905 Array array = Array.CreateInstance(elementType, convertedItems.Count);
5906 for (
int i = 0; i < convertedItems.Count; i++)
5908 object converted = ConvertValue(convertedItems[i], elementType);
5909 if (converted ==
null || elementType.IsAssignableFrom(converted.GetType()))
5910 array.SetValue(converted, i);
5912 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"Unable to convert [{DescribeForLog(converted)}] to [{elementType.Name}]", converted, GPALObjectType.None);
5918 if (underlying.IsGenericType)
5920 Type[] genericArgs = underlying.GetGenericArguments();
5921 Type elementType = genericArgs[0];
5923 Type listType = typeof(List<>).MakeGenericType(elementType);
5924 IList list = (IList)Activator.CreateInstance(listType)!;
5926 foreach (var rawItem
in convertedItems)
5928 object convertedItem = ConvertValue(rawItem, elementType);
5929 if (convertedItem ==
null || elementType.IsAssignableFrom(convertedItem.GetType()))
5930 list.Add(convertedItem);
5932 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"Unable to convert [{DescribeForLog(convertedItem)}] to [{elementType.Name}]", convertedItem, GPALObjectType.None);
5936 if (underlying.IsGenericType &&
5937 underlying.GetGenericTypeDefinition() == typeof(List<>))
5946 var targetListType = targetCollectionType;
5947 var ctor = targetListType.GetConstructor(
new[] { typeof(IEnumerable<>).MakeGenericType(elementType) });
5949 return ctor.Invoke(
new object[] { list });
5952 ctor = targetListType.GetConstructor(
new[] { listType });
5954 return ctor.Invoke(
new object[] { list });
5963 IList nonGenericList =
new ArrayList();
5964 foreach (var item
in convertedItems)
5965 nonGenericList.Add(item);
5966 return nonGenericList;
5969 private static object ConvertDictionaryToStruct(
5970 IDictionary<string, object> dict,
5972 HashSet<object> visited)
5975 object instance = Activator.CreateInstance(structType);
5977 foreach (var prop
in structType.GetProperties(BindingFlags.Public | BindingFlags.Instance))
5982 if (dict.TryGetValue(prop.Name, out var rawValue))
5984 object converted = ConvertValue(
5989 prop.SetValue(instance, converted);
5997 public static object NormalizeForCleanJson(
object input)
6000 if (input is Dictionary<object, dynamic> dict)
6003 bool hasXmlMarkers = dict.Keys.Cast<
object>().Any(k => k.ToString().StartsWith(
"@GPAL") || k.ToString() ==
"#text");
6014 bool hasComplexKey = dict.Keys.Cast<
object>().Any(k => k !=
null &&
false == IsSimpleType(k.GetType()));
6017 var pairList =
new List<object>();
6018 foreach (var kvp
in dict)
6020 pairList.Add(
new Dictionary<string, object>
6022 [
"Key"] = NormalizeForCleanJson(ConvertClassToDictionary(kvp.Key)),
6023 [
"Value"] = NormalizeForCleanJson(kvp.Value)
6030 var result =
new Dictionary<string, object>();
6031 foreach (var kvp
in dict)
6033 string key = kvp.Key.ToString().Replace(
"@GPAL_",
"");
6034 result[key] = NormalizeForCleanJson(kvp.Value);
6040 var clean =
new Dictionary<string, object>();
6042 string textValue =
null;
6043 if (dict.TryGetValue(
"#text", out var txt))
6045 textValue = txt?.ToString();
6049 foreach (var kvp
in dict)
6051 string key = kvp.Key.ToString();
6053 if (key.StartsWith(
"@GPAL_"))
6055 clean[key.Substring(6)] = kvp.Value;
6060 foreach (var kvp
in dict)
6062 string key = kvp.Key.ToString();
6063 if (key.StartsWith(
"@GPAL_") || key ==
"#text")
continue;
6065 object child = NormalizeForCleanJson(kvp.Value);
6072 if (textValue !=
null && clean.Count > 0)
6074 clean[
"value"] = textValue;
6079 if (textValue !=
null && clean.Count == 0)
6081 if (
double.TryParse(textValue, out
double num))
6088 else if (input is List<Dictionary<object, dynamic>> list)
6090 return list.Select(NormalizeForCleanJson).ToList();
6092 else if (input is List<object> objList)
6094 return objList.Select(NormalizeForCleanJson).ToList();
6107 object phase1 = StripAndGroup(input);
6110 return UnwrapSingleItemLists(phase1);
6113 private static object StripAndGroup(
object input)
6115 if (input ==
null)
return null;
6117 if (input is IDictionary dict)
6119 var grouped =
new Dictionary<string, List<object>>(StringComparer.Ordinal);
6121 foreach (DictionaryEntry entry
in dict)
6123 string originalKey = entry.Key?.ToString() ??
string.Empty;
6124 string cleanedKey = originalKey;
6126 if (cleanedKey.StartsWith(
"@GPAL_"))
6127 cleanedKey = cleanedKey.Substring(6);
6128 else if (cleanedKey.StartsWith(
"@"))
6129 cleanedKey = cleanedKey.Substring(1);
6131 object value = StripAndGroup(entry.Value);
6138 if (value is List<object> singleWrap && singleWrap.Count == 1 && singleWrap[0] is IDictionary)
6139 value = singleWrap[0];
6142 if (
string.IsNullOrEmpty(cleanedKey) && value is
string str && !
string.IsNullOrWhiteSpace(str))
6143 cleanedKey =
"value";
6145 else if (cleanedKey ==
"#text")
6146 cleanedKey =
"value";
6147 else if (cleanedKey.StartsWith(
"GPALKEY"))
6148 cleanedKey = cleanedKey.Substring(7);
6154 else if (System.Text.RegularExpressions.Regex.IsMatch(cleanedKey,
@"^GPAL\d+_"))
6155 cleanedKey = System.Text.RegularExpressions.Regex.Replace(cleanedKey,
@"^GPAL\d+_",
"");
6157 if (!grouped.TryGetValue(cleanedKey, out var list))
6159 list =
new List<object>();
6160 grouped[cleanedKey] = list;
6165 var result =
new Dictionary<string, object>(StringComparer.Ordinal);
6166 foreach (var kvp
in grouped)
6168 result[kvp.Key] = kvp.Value.Count == 1 ? kvp.Value[0] : (object)kvp.Value;
6171 if (result.Count == 1 && result.ContainsKey(
"value"))
6173 return result[
"value"];
6178 if (input is IEnumerable enumerable && !(input is
string))
6180 var list =
new List<object>();
6181 foreach (var item
in enumerable)
6183 list.Add(StripAndGroup(item));
6185 return list.Count == 0 ? null : list;
6191 private static object UnwrapSingleItemLists(
object input)
6193 if (input ==
null)
return null;
6197 if (input is IList list && list.Count == 1)
6199 object inner = UnwrapSingleItemLists(list[0]);
6200 if (inner ==
null || inner is
string || inner is ValueType)
6206 return new List<object> { inner };
6211 if (input is IList multiList && multiList.Count > 1)
6213 var unwrapped =
new List<object>();
6214 foreach (var item
in multiList)
6216 unwrapped.Add(UnwrapSingleItemLists(item));
6222 if (input is IDictionary dict)
6224 var result =
new Dictionary<string, object>(StringComparer.Ordinal);
6225 foreach (DictionaryEntry entry
in dict)
6227 string key = entry.Key?.ToString() ??
"";
6228 object value = UnwrapSingleItemLists(entry.Value);
6229 result[key] = value;
6232 if (result.Count == 1 && result.ContainsKey(
"value"))
6233 return result[
"value"];
6237 if (result.Count == 1 && result.ContainsKey(
"item"))
6239 var itemVal = result[
"item"];
6240 return itemVal is IList ? itemVal :
new List<object> { itemVal };
6248 internal static string GetXmlHeaderFromFile(
string inputFilename)
6250 if (!File.Exists(inputFilename))
6251 return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
6253 var lines = File.ReadAllLines(inputFilename);
6254 var headerLines =
new List<string>();
6256 foreach (var line
in lines)
6258 string trimmed = line.Trim();
6261 if (trimmed.StartsWith(
"<") && !trimmed.StartsWith(
"<?") && !trimmed.StartsWith(
"<!DOCTYPE") && !trimmed.StartsWith(
"<!--"))
6267 if (trimmed.StartsWith(
"<?xml") ||
6268 trimmed.StartsWith(
"<?xml-stylesheet") ||
6269 trimmed.StartsWith(
"<!DOCTYPE") ||
6270 trimmed.StartsWith(
"<!--"))
6272 headerLines.Add(line);
6277 if (headerLines.Count == 0)
6278 return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
6280 return string.Join(Environment.NewLine, headerLines) + Environment.NewLine;
6283 public static bool TryConvertToWaitTime(
object value, out WaitTime result)
6285 result = WaitTime.Immediate;
6290 GPALEventType.WARNING,
6291 "WaitTime treating NULL as ZERO",
6293 GPALObjectType.Converter);
6299 if (value is WaitTime wt)
6311 if (value is
string s)
6313 if (WaitTime.TryFromString(s, out result))
6317 if (WaitTime.TryFromDictionary((IDictionary<object, object>)value, out result))
6323 GPALEventType.WARNING,
6324 $
"WaitTime conversion failed: unsupported type [{value.GetType().FullName}]",
6326 GPALObjectType.Converter);
6330 internal static List<string> SplitRespectingQuotes(
string line,
char delimiter)
6332 var tokens =
new List<string>();
6333 var sb =
new StringBuilder();
6334 bool inQuotes =
false;
6336 for (
int i = 0; i < line.Length; i++)
6343 if (inQuotes && i + 1 < line.Length && line[i + 1] ==
'"')
6350 inQuotes = !inQuotes;
6353 else if (c == delimiter && !inQuotes)
6355 tokens.Add(sb.ToString().Trim());
6358 else if (c == delimiter && inQuotes)
6371 tokens.Add(sb.ToString().Trim());
6374 internal static string ExtractRawXmlFromBrowserSource(
string fullPageSource)
6376 if (
string.IsNullOrWhiteSpace(fullPageSource))
6377 return string.Empty;
6380 int startIndex = fullPageSource.IndexOf(
"<div id=\"webkit-xml-viewer-source-xml\">");
6381 if (startIndex == -1)
6384 if (fullPageSource.TrimStart().StartsWith(
"<?xml"))
6385 return fullPageSource;
6388 GPALEventType.DEBUG,
6389 "No webkit-xml-viewer wrapper found and content doesn't start with <?xml. Returning empty.",
6391 GPALObjectType.Other);
6392 return string.Empty;
6395 startIndex +=
"<div id=\"webkit-xml-viewer-source-xml\">".Length;
6397 int endIndex = fullPageSource.IndexOf(
"</div>", startIndex);
6398 if (endIndex == -1) endIndex = fullPageSource.Length;
6400 string innerHtml = fullPageSource.Substring(startIndex, endIndex - startIndex).Trim();
6407 innerHtml = innerHtml.Replace(
"<![CDATA[",
"").Replace(
"]]>",
"");
6411 #endregion <Helpers>