/// <summary> /// Creates a new instance of the indicated type, populating it with the dictionary. /// Can use any constructor of the indicated type, provided that there are keys in the /// dictionary that correspond (case sensitive) to the names of the constructor parameters. /// </summary> /// <param name="source">The source of values.</param> /// <param name="type">The type to create.</param> /// <param name="mappedType"> /// GraphType for matching dictionary keys with <paramref name="type"/> property names. /// GraphType contains information about this matching in Metadata property. /// In case of configuring field as Field(x => x.FName).Name("FirstName") source dictionary /// will have 'FirstName' key but its value should be set to 'FName' property of created object. /// </param> public static object ToObject(this IDictionary <string, object> source, Type type, IGraphType mappedType = null) { // Given Field(x => x.FName).Name("FirstName") and key == "FirstName" returns "FName" string GetPropertyName(string key, out FieldType field) { var complexType = mappedType.GetNamedType() as IComplexGraphType; // type may not contain mapping information field = complexType?.GetField(key); return(field?.GetMetadata(ComplexGraphType <object> .ORIGINAL_EXPRESSION_PROPERTY_NAME, key) ?? key); } // Returns keys from source that match constructor signature string[] MatchSourceKeys(ParameterInfo[] parameters) { // parameterless constructors are the most common use case if (parameters.Length == 0) { return(Array.Empty <string>()); } // otherwise we have to iterate over the parameters - worse performance but this is rather rare case List <string> keys = null; return(parameters.All(p => source.Any(keyValue => { bool matched = string.Equals(GetPropertyName(keyValue.Key, out var _), p.Name, StringComparison.InvariantCultureIgnoreCase); if (matched) { (keys ??= new List <string>()).Add(keyValue.Key); } return matched; })) ? keys.ToArray() : null); } if (source == null) { throw new ArgumentNullException(nameof(source)); } // force sourceType to be IDictionary<string, object> if (ValueConverter.TryConvertTo(source, type, out object result, typeof(IDictionary <string, object>))) { return(result); } // attempt to use the most specific constructor sorting in decreasing order of parameters number var ctorCandidates = _types.GetOrAdd(type, t => t.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).OrderByDescending(ctor => ctor.GetParameters().Length).ToArray()); ConstructorInfo targetCtor = null; ParameterInfo[] ctorParameters = null; string[] matchedKeys = null; foreach (var ctor in ctorCandidates) { var parameters = ctor.GetParameters(); matchedKeys = MatchSourceKeys(parameters); if (matchedKeys != null) { targetCtor = ctor; ctorParameters = parameters; break; } } if (targetCtor == null) { throw new ArgumentException($"Type '{type}' does not contain a constructor that could be used for current input arguments.", nameof(type)); } object[] ctorArguments = ctorParameters.Length == 0 ? Array.Empty <object>() : new object[ctorParameters.Length]; for (int i = 0; i < ctorParameters.Length; ++i) { object arg = GetPropertyValue(source[matchedKeys[i]], ctorParameters[i].ParameterType); ctorArguments[i] = arg; } object obj = targetCtor.Invoke(ctorArguments); foreach (var item in source) { // these parameters have already been used in the constructor, no need to set property if (matchedKeys.Length > 0 && matchedKeys.Any(k => k == item.Key)) { continue; } string propertyName = GetPropertyName(item.Key, out var field); PropertyInfo propertyInfo = null; try { propertyInfo = type.GetProperty(propertyName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance); } catch (AmbiguousMatchException) { propertyInfo = type.GetProperty(propertyName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); } if (propertyInfo?.CanWrite == true) { object value = GetPropertyValue(item.Value, propertyInfo.PropertyType, field?.ResolvedType); propertyInfo.SetValue(obj, value, null); //issue: this works even if propertyInfo is ValueType and value is null } } return(obj); }
private static object ConvertValue(object value, Type targetType) { return(ValueConverter.ConvertTo(value, targetType)); }
/// <summary> /// Converts the indicated value into a type that is compatible with fieldType. /// </summary> /// <param name="propertyValue">The value to be converted.</param> /// <param name="fieldType">The desired type.</param> /// <param name="mappedType"> /// GraphType for matching dictionary keys with <paramref name="type"/> property names. /// GraphType contains information about this matching in Metadata property. /// In case of configuring field as Field(x => x.FName).Name("FirstName") source dictionary /// will have 'FirstName' key but its value should be set to 'FName' property of created object. /// </param> /// <remarks>There is special handling for strings, IEnumerable<T>, Nullable<T>, and Enum.</remarks> public static object GetPropertyValue(this object propertyValue, Type fieldType, IGraphType mappedType = null) { // Short-circuit conversion if the property value already of the right type if (propertyValue == null || fieldType == typeof(object) || fieldType.IsInstanceOfType(propertyValue)) { return(propertyValue); } if (ValueConverter.TryConvertTo(propertyValue, fieldType, out object result)) { return(result); } var enumerableInterface = fieldType.Name == "IEnumerable`1" ? fieldType : fieldType.GetInterface("IEnumerable`1"); if (fieldType != typeof(string) && enumerableInterface != null) { IList newCollection; var elementType = enumerableInterface.GetGenericArguments()[0]; var underlyingType = Nullable.GetUnderlyingType(elementType) ?? elementType; bool fieldTypeImplementsIList = fieldType.GetInterface("IList") != null; var propertyValueAsIList = propertyValue as IList; // Custom container if (fieldTypeImplementsIList && !fieldType.IsArray) { newCollection = ( IList )Activator.CreateInstance(fieldType); } // Array of known size is created immediately else if (fieldType.IsArray && propertyValueAsIList != null) { newCollection = Array.CreateInstance(elementType, propertyValueAsIList.Count); } // List<T> else { var genericListType = typeof(List <>).MakeGenericType(elementType); newCollection = ( IList )Activator.CreateInstance(genericListType); } if (!(propertyValue is IEnumerable valueList)) { return(newCollection); } // Array of known size is populated in-place if (fieldType.IsArray && propertyValueAsIList != null) { for (int i = 0; i < propertyValueAsIList.Count; ++i) { object listItem = propertyValueAsIList[i]; newCollection[i] = listItem == null ? null : GetPropertyValue(listItem, underlyingType, mappedType); } } // Array of unknown size is created only after populating list else { foreach (object listItem in valueList) { _ = newCollection.Add(listItem == null ? null : GetPropertyValue(listItem, underlyingType, mappedType)); } if (fieldType.IsArray) { newCollection = (( dynamic )newCollection).ToArray(); } } return(newCollection); } object value = propertyValue; var nullableFieldType = Nullable.GetUnderlyingType(fieldType); // if this is a nullable type and the value is null, return null if (nullableFieldType != null && value == null) { return(null); } if (nullableFieldType != null) { fieldType = nullableFieldType; } if (propertyValue is IDictionary <string, object> objects) { return(ToObject(objects, fieldType, mappedType)); } if (fieldType.IsEnum) { if (value == null) { string[] enumNames = Enum.GetNames(fieldType); value = enumNames[0]; } if (!IsDefinedEnumValue(fieldType, value)) { throw new ExecutionError($"Unknown value '{value}' for enum '{fieldType.Name}'."); } string str = value.ToString(); value = Enum.Parse(fieldType, str, true); } return(ValueConverter.ConvertTo(value, fieldType)); }