private void InitializeIfRequired() { if (_dictionary != null) { return; } bool caseInsensitive = Options.HasValue ? Options.Value.PropertyNameCaseInsensitive : false; var dictionary = new JsonPropertyDictionary <JsonNode>(caseInsensitive); if (_jsonElement.HasValue) { JsonElement jElement = _jsonElement.Value; foreach (JsonProperty jElementProperty in jElement.EnumerateObject()) { JsonNode?node = JsonNodeConverter.Create(jElementProperty.Value, Options); if (node != null) { node.Parent = this; } dictionary.Add(new KeyValuePair <string, JsonNode?>(jElementProperty.Name, node)); } _jsonElement = null; } _dictionary = dictionary; }
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); }
internal void CacheMember(JsonPropertyInfo jsonPropertyInfo, JsonPropertyDictionary <JsonPropertyInfo>?propertyCache, ref Dictionary <string, JsonPropertyInfo>?ignoredMembers) { string memberName = jsonPropertyInfo.ClrName !; // The JsonPropertyNameAttribute or naming policy resulted in a collision. if (!propertyCache !.TryAdd(jsonPropertyInfo.Name, jsonPropertyInfo)) { JsonPropertyInfo other = propertyCache[jsonPropertyInfo.Name] !; if (other.IsIgnored) { // Overwrite previously cached property since it has [JsonIgnore]. propertyCache[jsonPropertyInfo.Name] = jsonPropertyInfo; } else if ( // Does the current property have `JsonIgnoreAttribute`? !jsonPropertyInfo.IsIgnored && // Is the current property hidden by the previously cached property // (with `new` keyword, or by overriding)? other.ClrName != memberName && // Was a property with the same CLR name was ignored? That property hid the current property, // thus, if it was ignored, the current property should be ignored too. ignoredMembers?.ContainsKey(memberName) != true) { // We throw if we have two public properties that have the same JSON property name, and neither have been ignored. ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameConflict(Type, jsonPropertyInfo); } // Ignore the current property. } if (jsonPropertyInfo.IsIgnored) { (ignoredMembers ??= new Dictionary <string, JsonPropertyInfo>()).Add(memberName, jsonPropertyInfo); } }
private void AddPropertiesAndParametersUsingReflection() { Debug.Assert(PropertyInfoForTypeInfo.ConverterStrategy == ConverterStrategy.Object); const BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly; Dictionary <string, JsonPropertyInfo>?ignoredMembers = null; PropertyInfo[] properties = Type.GetProperties(bindingFlags); bool propertyOrderSpecified = false; // PropertyCache is not accessed by other threads until the current JsonTypeInfo instance // is finished initializing and added to the cache on JsonSerializerOptions. // Default 'capacity' to the common non-polymorphic + property case. PropertyCache = new JsonPropertyDictionary <JsonPropertyInfo>(Options.PropertyNameCaseInsensitive, capacity: properties.Length); // We start from the most derived type. Type?currentType = Type; while (true) { foreach (PropertyInfo propertyInfo in properties) { bool isVirtual = propertyInfo.IsVirtual(); string propertyName = propertyInfo.Name; // Ignore indexers and virtual properties that have overrides that were [JsonIgnore]d. if (propertyInfo.GetIndexParameters().Length > 0 || PropertyIsOverridenAndIgnored(propertyName, propertyInfo.PropertyType, isVirtual, ignoredMembers)) { continue; } // For now we only support public properties (i.e. setter and/or getter is public). if (propertyInfo.GetMethod?.IsPublic == true || propertyInfo.SetMethod?.IsPublic == true) { CacheMember( currentType, propertyInfo.PropertyType, propertyInfo, isVirtual, NumberHandling, ref propertyOrderSpecified, ref ignoredMembers); } else { if (JsonPropertyInfo.GetAttribute <JsonIncludeAttribute>(propertyInfo) != null) { ThrowHelper.ThrowInvalidOperationException_JsonIncludeOnNonPublicInvalid(propertyName, currentType); } // Non-public properties should not be included for (de)serialization. } } foreach (FieldInfo fieldInfo in currentType.GetFields(bindingFlags)) { string fieldName = fieldInfo.Name; if (PropertyIsOverridenAndIgnored(fieldName, fieldInfo.FieldType, currentMemberIsVirtual: false, ignoredMembers)) { continue; } bool hasJsonInclude = JsonPropertyInfo.GetAttribute <JsonIncludeAttribute>(fieldInfo) != null; if (fieldInfo.IsPublic) { if (hasJsonInclude || Options.IncludeFields) { CacheMember( currentType, fieldInfo.FieldType, fieldInfo, isVirtual: false, NumberHandling, ref propertyOrderSpecified, ref ignoredMembers); } } else { if (hasJsonInclude) { ThrowHelper.ThrowInvalidOperationException_JsonIncludeOnNonPublicInvalid(fieldName, currentType); } // Non-public fields should not be included for (de)serialization. } } currentType = currentType.BaseType; if (currentType == null) { break; } properties = currentType.GetProperties(bindingFlags); } ; if (propertyOrderSpecified) { PropertyCache.List.Sort((p1, p2) => p1.Value !.Order.CompareTo(p2.Value !.Order)); } }
internal override void LateAddProperties() { Debug.Assert(!IsConfigured); Debug.Assert(PropertyCache is null); if (Kind != JsonTypeInfoKind.Object) { return; } JsonSerializerContext?context = Options.SerializerContext; JsonPropertyInfo[] array; if (PropInitFunc == null || (array = PropInitFunc(context !)) == null) { if (typeof(T) == typeof(object)) { return; } if (Converter.ElementType != null) { // Nullable<> or F# optional converter's strategy is set to element's strategy return; } if (SerializeHandler == null) { ThrowHelper.ThrowInvalidOperationException_NoMetadataForTypeProperties(Options.TypeInfoResolver, Type); } MetadataSerializationNotSupported = true; return; } Dictionary <string, JsonPropertyInfo>? ignoredMembers = null; JsonPropertyDictionary <JsonPropertyInfo> propertyCache = CreatePropertyCache(capacity: array.Length); for (int i = 0; i < array.Length; i++) { JsonPropertyInfo jsonPropertyInfo = array[i]; bool hasJsonInclude = jsonPropertyInfo.SrcGen_HasJsonInclude; if (!jsonPropertyInfo.SrcGen_IsPublic) { if (hasJsonInclude) { Debug.Assert(jsonPropertyInfo.MemberName != null, "MemberName is not set by source gen"); ThrowHelper.ThrowInvalidOperationException_JsonIncludeOnNonPublicInvalid(jsonPropertyInfo.MemberName, jsonPropertyInfo.DeclaringType); } continue; } if (jsonPropertyInfo.MemberType == MemberTypes.Field && !hasJsonInclude && !Options.IncludeFields) { continue; } CacheMember(jsonPropertyInfo, propertyCache, ref ignoredMembers); } PropertyCache = propertyCache; }
internal void AddPropertiesUsingSourceGenInfo() { if (PropertyInfoForTypeInfo.ConverterStrategy != ConverterStrategy.Object) { return; } JsonSerializerContext?context = Options.SerializerContext; JsonPropertyInfo[] array; if (PropInitFunc == null || (array = PropInitFunc(context !)) == null) { if (typeof(T) == typeof(object)) { return; } if (Converter.ElementType != null) { // Nullable<> or F# optional converter's strategy is set to element's strategy return; } if (SerializeHandler != null && Options.SerializerContext?.CanUseSerializationLogic == true) { ThrowOnDeserialize = true; return; } ThrowHelper.ThrowInvalidOperationException_NoMetadataForTypeProperties(context, Type); return; } Dictionary <string, JsonPropertyInfo>? ignoredMembers = null; JsonPropertyDictionary <JsonPropertyInfo> propertyCache = CreatePropertyCache(capacity: array.Length); for (int i = 0; i < array.Length; i++) { JsonPropertyInfo jsonPropertyInfo = array[i]; bool hasJsonInclude = jsonPropertyInfo.SrcGen_HasJsonInclude; if (!jsonPropertyInfo.SrcGen_IsPublic) { if (hasJsonInclude) { Debug.Assert(jsonPropertyInfo.ClrName != null, "ClrName is not set by source gen"); ThrowHelper.ThrowInvalidOperationException_JsonIncludeOnNonPublicInvalid(jsonPropertyInfo.ClrName, jsonPropertyInfo.DeclaringType); } continue; } if (jsonPropertyInfo.MemberType == MemberTypes.Field && !hasJsonInclude && !Options.IncludeFields) { continue; } if (jsonPropertyInfo.SrcGen_IsExtensionData) { if (ExtensionDataProperty != null) { ThrowHelper.ThrowInvalidOperationException_SerializationDuplicateTypeAttribute(Type, typeof(JsonExtensionDataAttribute)); } ExtensionDataProperty = jsonPropertyInfo; continue; } CacheMember(jsonPropertyInfo, propertyCache, ref ignoredMembers); } PropertyCache = propertyCache; }