internal void InitializeConstructorParameters(JsonParameterInfoValues[] jsonParameters, bool sourceGenMode = false) { var parameterCache = new JsonPropertyDictionary <JsonParameterInfo>(Options.PropertyNameCaseInsensitive, jsonParameters.Length); // Cache the lookup from object property name to JsonPropertyInfo using a case-insensitive comparer. // Case-insensitive is used to support both camel-cased parameter names and exact matches when C# // record types or anonymous types are used. // The property name key does not use [JsonPropertyName] or PropertyNamingPolicy since we only bind // the parameter name to the object property name and do not use the JSON version of the name here. var nameLookup = new Dictionary <ParameterLookupKey, ParameterLookupValue>(PropertyCache !.Count); foreach (KeyValuePair <string, JsonPropertyInfo?> kvp in PropertyCache.List) { JsonPropertyInfo jsonProperty = kvp.Value !; string propertyName = jsonProperty.ClrName !; ParameterLookupKey key = new(propertyName, jsonProperty.PropertyType); ParameterLookupValue value = new(jsonProperty); if (!JsonHelpers.TryAdd(nameLookup, key, value)) { // More than one property has the same case-insensitive name and Type. // Remember so we can throw a nice exception if this property is used as a parameter name. ParameterLookupValue existing = nameLookup[key]; existing.DuplicateName = propertyName; } } foreach (JsonParameterInfoValues parameterInfo in jsonParameters) { ParameterLookupKey paramToCheck = new(parameterInfo.Name, parameterInfo.ParameterType); if (nameLookup.TryGetValue(paramToCheck, out ParameterLookupValue? matchingEntry)) { if (matchingEntry.DuplicateName != null) { // Multiple object properties cannot bind to the same constructor parameter. ThrowHelper.ThrowInvalidOperationException_MultiplePropertiesBindToConstructorParameters( Type, parameterInfo.Name !, matchingEntry.JsonPropertyInfo.Name, matchingEntry.DuplicateName); } Debug.Assert(matchingEntry.JsonPropertyInfo != null); JsonPropertyInfo jsonPropertyInfo = matchingEntry.JsonPropertyInfo; JsonParameterInfo jsonParameterInfo = CreateConstructorParameter(parameterInfo, jsonPropertyInfo, sourceGenMode, Options); parameterCache.Add(jsonPropertyInfo.Name, jsonParameterInfo); } // It is invalid for the extension data property to bind with a constructor argument. else if (DataExtensionProperty != null && StringComparer.OrdinalIgnoreCase.Equals(paramToCheck.Name, DataExtensionProperty.Name)) { ThrowHelper.ThrowInvalidOperationException_ExtensionDataCannotBindToCtorParam(DataExtensionProperty); } } ParameterCount = jsonParameters.Length; Volatile.Write(ref ParameterCache, parameterCache); }
// Create a parameter that is ignored at run-time. It uses the same type (typeof(sbyte)) to help // prevent issues with unsupported types and helps ensure we don't accidently (de)serialize it. public static JsonParameterInfo CreateIgnoredParameterPlaceholder(JsonParameterInfoValues parameterInfo, JsonPropertyInfo matchingProperty) { JsonParameterInfo jsonParameterInfo = matchingProperty.ConverterBase.CreateJsonParameterInfo(); jsonParameterInfo.ClrInfo = parameterInfo; jsonParameterInfo.RuntimePropertyType = matchingProperty.RuntimePropertyType !; jsonParameterInfo.NameAsUtf8Bytes = matchingProperty.NameAsUtf8Bytes !; jsonParameterInfo.InitializeDefaultValue(matchingProperty); return(jsonParameterInfo); }
private static JsonParameterInfo CreateConstructorParameter( JsonParameterInfoValues parameterInfo, JsonPropertyInfo jsonPropertyInfo, bool sourceGenMode, JsonSerializerOptions options) { if (jsonPropertyInfo.IsIgnored) { return(JsonParameterInfo.CreateIgnoredParameterPlaceholder(parameterInfo, jsonPropertyInfo, sourceGenMode)); } JsonConverter converter = jsonPropertyInfo.ConverterBase; JsonParameterInfo jsonParameterInfo = converter.CreateJsonParameterInfo(); jsonParameterInfo.Initialize(parameterInfo, jsonPropertyInfo, options); return(jsonParameterInfo); }
/// <summary> /// Create a parameter that is ignored at run time. It uses the same type (typeof(sbyte)) to help /// prevent issues with unsupported types and helps ensure we don't accidently (de)serialize it. /// </summary> public static JsonParameterInfo CreateIgnoredParameterPlaceholder( JsonParameterInfoValues parameterInfo, JsonPropertyInfo matchingProperty, bool sourceGenMode) { JsonParameterInfo jsonParameterInfo = new JsonParameterInfo <sbyte>(); jsonParameterInfo.ClrInfo = parameterInfo; jsonParameterInfo.PropertyType = matchingProperty.PropertyType; jsonParameterInfo.NameAsUtf8Bytes = matchingProperty.NameAsUtf8Bytes !; // TODO: https://github.com/dotnet/runtime/issues/60082. // Default value initialization for params mapping to ignored properties doesn't // account for the default value of optional parameters. This should be fixed. if (sourceGenMode) { // The <T> value in the matching JsonPropertyInfo<T> instance matches the parameter type. jsonParameterInfo.DefaultValue = matchingProperty.DefaultValue; } else { // The <T> value in the created JsonPropertyInfo<T> instance (sbyte) // doesn't match the parameter type, use reflection to get the default value. Type parameterType = parameterInfo.ParameterType; DefaultValueHolder holder; if (matchingProperty.Options.TryGetJsonTypeInfo(parameterType, out JsonTypeInfo? typeInfo)) { holder = typeInfo.DefaultValueHolder; } else { holder = DefaultValueHolder.CreateHolder(parameterInfo.ParameterType); } jsonParameterInfo.DefaultValue = holder.DefaultValue; } return(jsonParameterInfo); }