{
internal partial class JsonClassInfo
{
- private JsonPropertyInfo AddPolicyProperty(Type propertyType, JsonSerializerOptions options)
+ private void AddPolicyProperty(Type propertyType, JsonSerializerOptions options)
{
// A policy property is not a real property on a type; instead it leverages the existing converter
// logic and generic support to avoid boxing. It is used with values types and elements from collections and
// dictionaries. Typically it would represent a CLR type such as System.String.
- return AddProperty(
+ PolicyProperty = AddProperty(
propertyType,
propertyInfo : null, // Not a real property so this is null.
classType : typeof(object), // A dummy type (not used).
options : options);
-
}
+
private JsonPropertyInfo AddProperty(Type propertyType, PropertyInfo propertyInfo, Type classType, JsonSerializerOptions options)
{
JsonPropertyInfo jsonInfo = CreateProperty(propertyType, propertyType, propertyInfo, classType, options);
}
}
- if (propertyInfo != null)
- {
- _propertyRefs.Add(new PropertyRef(GetKey(jsonInfo.NameUsedToCompare), jsonInfo));
- }
- else
- {
- // A single property or an IEnumerable
- _propertyRefs.Add(new PropertyRef(0, jsonInfo));
- }
-
return jsonInfo;
}
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
+using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text.Json.Serialization;
// The length of the property name embedded in the key (in bytes).
private const int PropertyNameKeyLength = 6;
- private readonly List<PropertyRef> _propertyRefs = new List<PropertyRef>();
+ // The limit to how many property names from the JSON are cached before using PropertyCache.
+ private const int PropertyNameCountCacheThreshold = 64;
- // Cache of properties by first ordering. Use an array for highest performance.
+ // The properties on a POCO keyed on property name.
+ public volatile Dictionary<string, JsonPropertyInfo> PropertyCache;
+
+ // Cache of properties by first JSON ordering. Use an array for highest performance.
private volatile PropertyRef[] _propertyRefsSorted = null;
internal delegate object ConstructorDelegate();
// If enumerable, the JsonClassInfo for the element type.
internal JsonClassInfo ElementClassInfo { get; private set; }
+ public JsonSerializerOptions Options { get; private set; }
+
public Type Type { get; private set; }
internal void UpdateSortedPropertyCache(ref ReadStackFrame frame)
{
- // Todo: on classes with many properties (about 50) we need to switch to a hashtable for performance.
- // Todo: when using PropertyNameCaseInsensitive we also need to use the hashtable with case-insensitive
- // comparison to handle Turkish etc. cultures properly.
-
- Debug.Assert(_propertyRefs != null);
-
- // Set the sorted property cache. Overwrite any existing cache which can occur in multi-threaded cases.
- if (frame.PropertyRefCache != null)
+ // Check if we are trying to build the sorted cache.
+ if (frame.PropertyRefCache == null)
{
- HashSet<PropertyRef> cache = frame.PropertyRefCache;
-
- // Add any missing properties. This creates a consistent cache count which is important for
- // the loop in GetProperty() when there are multiple threads in a race conditions each generating
- // a cache for a given POCO type but with different property counts in the json.
- while (cache.Count < _propertyRefs.Count)
- {
- for (int iProperty = 0; iProperty < _propertyRefs.Count; iProperty++)
- {
- PropertyRef propertyRef = _propertyRefs[iProperty];
- // Cache the missing property or override the existing property.
- cache.Add(propertyRef);
- }
- }
+ return;
+ }
- Debug.Assert(cache.Count == _propertyRefs.Count);
- if (_propertyRefsSorted == null || _propertyRefsSorted.Length < cache.Count)
- {
- _propertyRefsSorted = new PropertyRef[cache.Count];
- }
- cache.CopyTo(_propertyRefsSorted);
- frame.PropertyRefCache = null;
+ List<PropertyRef> newList;
+ if (_propertyRefsSorted != null)
+ {
+ newList = new List<PropertyRef>(_propertyRefsSorted);
+ newList.AddRange(frame.PropertyRefCache);
+ _propertyRefsSorted = newList.ToArray();
}
+ else
+ {
+ _propertyRefsSorted = frame.PropertyRefCache.ToArray();
+ }
+
+ frame.PropertyRefCache = null;
}
internal JsonClassInfo(Type type, JsonSerializerOptions options)
{
Type = type;
+ Options = options;
ClassType = GetClassType(type, options);
CreateObject = options.MemberAccessorStrategy.CreateConstructor(type);
{
case ClassType.Object:
{
- var propertyNames = new HashSet<string>(StringComparer.Ordinal);
+ PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
+
+ Dictionary<string, JsonPropertyInfo> cache = CreatePropertyCache(properties.Length);
- foreach (PropertyInfo propertyInfo in type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
+ foreach (PropertyInfo propertyInfo in properties)
{
// Ignore indexers
if (propertyInfo.GetIndexParameters().Length > 0)
{
JsonPropertyInfo jsonPropertyInfo = AddProperty(propertyInfo.PropertyType, propertyInfo, type, options);
- Debug.Assert(jsonPropertyInfo.NameUsedToCompareAsString != null);
-
// If the JsonPropertyNameAttribute or naming policy results in collisions, throw an exception.
- if (!propertyNames.Add(jsonPropertyInfo.NameUsedToCompareAsString))
+ if (cache.ContainsKey(jsonPropertyInfo.NameAsString))
{
ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameConflict(this, jsonPropertyInfo);
}
- jsonPropertyInfo.ClearUnusedValuesAfterAdd();
+ cache.Add(jsonPropertyInfo.NameAsString, jsonPropertyInfo);
}
}
+ // Set as a unit to avoid concurrency issues.
+ PropertyCache = cache;
+
DetermineExtensionDataProperty();
}
break;
case ClassType.Dictionary:
{
// Add a single property that maps to the class type so we can have policies applied.
- JsonPropertyInfo policyProperty = AddPolicyProperty(type, options);
+ AddPolicyProperty(type, options);
// Use the type from the property policy to get any late-bound concrete types (from an interface like IDictionary).
- CreateObject = options.MemberAccessorStrategy.CreateConstructor(policyProperty.RuntimePropertyType);
+ CreateObject = options.MemberAccessorStrategy.CreateConstructor(PolicyProperty.RuntimePropertyType);
// Create a ClassInfo that maps to the element type which is used for (de)serialization and policies.
Type elementType = GetElementType(type, parentType: null, memberInfo: null, options: options);
}
break;
case ClassType.Value:
+ // Add a single property that maps to the class type so we can have policies applied.
+ AddPolicyProperty(type, options);
+ break;
case ClassType.Unknown:
// Add a single property that maps to the class type so we can have policies applied.
AddPolicyProperty(type, options);
+ PropertyCache = new Dictionary<string, JsonPropertyInfo>();
break;
default:
Debug.Fail($"Unexpected class type: {ClassType}");
private JsonPropertyInfo GetPropertyThatHasAttribute(Type attributeType)
{
- Debug.Assert(_propertyRefs != null);
+ Debug.Assert(PropertyCache != null);
JsonPropertyInfo property = null;
- for (int iProperty = 0; iProperty < _propertyRefs.Count; iProperty++)
+ foreach (JsonPropertyInfo jsonPropertyInfo in PropertyCache.Values)
{
- PropertyRef propertyRef = _propertyRefs[iProperty];
- JsonPropertyInfo jsonPropertyInfo = propertyRef.Info;
Attribute attribute = jsonPropertyInfo.PropertyInfo.GetCustomAttribute(attributeType);
if (attribute != null)
{
return property;
}
- internal JsonPropertyInfo GetProperty(JsonSerializerOptions options, ReadOnlySpan<byte> propertyName, ref ReadStackFrame frame)
+ internal JsonPropertyInfo GetProperty(ReadOnlySpan<byte> propertyName, ref ReadStackFrame frame)
{
- // If we should compare with case-insensitive, normalize to an uppercase format since that is what is cached on the propertyInfo.
- if (options.PropertyNameCaseInsensitive)
- {
- string utf16PropertyName = JsonHelpers.Utf8GetString(propertyName);
- string upper = utf16PropertyName.ToUpperInvariant();
- propertyName = Encoding.UTF8.GetBytes(upper);
- }
-
- ulong key = GetKey(propertyName);
JsonPropertyInfo info = null;
- // First try sorted lookup.
- int propertyIndex = frame.PropertyIndex;
-
// If we're not trying to build the cache locally, and there is an existing cache, then use it.
- bool hasPropertyCache = frame.PropertyRefCache == null && _propertyRefsSorted != null;
- if (hasPropertyCache)
+ if (_propertyRefsSorted != null)
{
+ ulong key = GetKey(propertyName);
+
+ // First try sorted lookup.
+ int propertyIndex = frame.PropertyIndex;
+
// This .Length is consistent no matter what json data intialized _propertyRefsSorted.
int count = _propertyRefsSorted.Length;
if (count != 0)
{
- int iForward = propertyIndex;
- int iBackward = propertyIndex - 1;
+ int iForward = Math.Min(propertyIndex, count);
+ int iBackward = iForward - 1;
while (iForward < count || iBackward >= 0)
{
if (iForward < count)
{
- if (TryIsPropertyRefEqual(ref _propertyRefsSorted[iForward], propertyName, key, ref info))
+ if (TryIsPropertyRefEqual(_propertyRefsSorted[iForward], propertyName, key, ref info))
{
return info;
}
if (iBackward >= 0)
{
- if (TryIsPropertyRefEqual(ref _propertyRefsSorted[iBackward], propertyName, key, ref info))
+ if (TryIsPropertyRefEqual(_propertyRefsSorted[iBackward], propertyName, key, ref info))
{
return info;
}
// Try the main list which has all of the properties in a consistent order.
// We could get here even when hasPropertyCache==true if there is a race condition with different json
// property ordering and _propertyRefsSorted is re-assigned while in the loop above.
- for (int i = 0; i < _propertyRefs.Count; i++)
+
+ string stringPropertyName = Encoding.UTF8.GetString(propertyName
+#if netstandard
+ .ToArray()
+#endif
+ );
+
+ if (PropertyCache.TryGetValue(stringPropertyName, out info))
{
- PropertyRef propertyRef = _propertyRefs[i];
- if (TryIsPropertyRefEqual(ref propertyRef, propertyName, key, ref info))
+ // For performance, only add to cache up to a threshold and then just use the dictionary.
+ int count;
+ if (_propertyRefsSorted != null)
{
- break;
+ count = _propertyRefsSorted.Length;
}
- }
-
- if (!hasPropertyCache)
- {
- if (propertyIndex == 0 && frame.PropertyRefCache == null)
+ else
{
- // Create the temporary list on first property access to prevent a partially filled List.
- frame.PropertyRefCache = new HashSet<PropertyRef>();
+ count = 0;
}
-
- if (info != null)
+
+ // Do a quick check for the stable (after warm-up) case.
+ if (count < PropertyNameCountCacheThreshold)
{
- Debug.Assert(frame.PropertyRefCache != null);
- frame.PropertyRefCache.Add(new PropertyRef(key, info));
+ if (frame.PropertyRefCache != null)
+ {
+ count += frame.PropertyRefCache.Count;
+ }
+
+ // Check again to fill up to the limit.
+ if (count < PropertyNameCountCacheThreshold)
+ {
+ if (frame.PropertyRefCache == null)
+ {
+ frame.PropertyRefCache = new List<PropertyRef>();
+ }
+
+ ulong key = info.PropertyNameKey;
+ PropertyRef propertyRef = new PropertyRef(key, info);
+ frame.PropertyRefCache.Add(propertyRef);
+ }
}
}
return info;
}
- internal JsonPropertyInfo GetPolicyProperty()
+ private Dictionary<string, JsonPropertyInfo> CreatePropertyCache(int capacity)
{
- Debug.Assert(_propertyRefs.Count == 1);
- return _propertyRefs[0].Info;
- }
-
- internal JsonPropertyInfo GetProperty(int index)
- {
- Debug.Assert(index < _propertyRefs.Count);
- return _propertyRefs[index].Info;
- }
+ StringComparer comparer;
- internal int PropertyCount
- {
- get
+ if (Options.PropertyNameCaseInsensitive)
+ {
+ comparer = StringComparer.OrdinalIgnoreCase;
+ }
+ else
{
- return _propertyRefs.Count;
+ comparer = StringComparer.Ordinal;
}
+
+ return new Dictionary<string, JsonPropertyInfo>(capacity, comparer);
}
- private static bool TryIsPropertyRefEqual(ref PropertyRef propertyRef, ReadOnlySpan<byte> propertyName, ulong key, ref JsonPropertyInfo info)
+ public JsonPropertyInfo PolicyProperty { get; private set; }
+
+ private static bool TryIsPropertyRefEqual(in PropertyRef propertyRef, ReadOnlySpan<byte> propertyName, ulong key, ref JsonPropertyInfo info)
{
if (key == propertyRef.Key)
{
if (propertyName.Length <= PropertyNameKeyLength ||
// We compare the whole name, although we could skip the first 6 bytes (but it's likely not any faster)
- propertyName.SequenceEqual(propertyRef.Info.NameUsedToCompare))
+ propertyName.SequenceEqual(propertyRef.Info.Name))
{
info = propertyRef.Info;
return true;
return false;
}
- private static ulong GetKey(ReadOnlySpan<byte> propertyName)
+ public static ulong GetKey(ReadOnlySpan<byte> propertyName)
{
ulong key;
int length = propertyName.Length;
public ClassType ClassType;
- // After the property is added, clear any state not used later.
- public void ClearUnusedValuesAfterAdd()
- {
- NameAsString = null;
- NameUsedToCompareAsString = null;
- }
-
public abstract JsonConverter ConverterBase { get; set; }
// Copy any settings defined at run-time to the new property.
public void CopyRuntimeSettingsTo(JsonPropertyInfo other)
{
- other.Name = Name;
- other.NameUsedToCompare = NameUsedToCompare;
other.EscapedName = EscapedName;
+ other.Name = Name;
+ other.PropertyNameKey = PropertyNameKey;
}
public abstract IList CreateConverterList();
// At this point propertyName is valid UTF16, so just call the simple UTF16->UTF8 encoder.
Name = Encoding.UTF8.GetBytes(NameAsString);
- // Set the compare name.
- if (Options.PropertyNameCaseInsensitive)
- {
- NameUsedToCompareAsString = NameAsString.ToUpperInvariant();
- NameUsedToCompare = Encoding.UTF8.GetBytes(NameUsedToCompareAsString);
- }
- else
- {
- NameUsedToCompareAsString = NameAsString;
- NameUsedToCompare = Name;
- }
-
// Cache the escaped name.
EscapedName = JsonEncodedText.Encode(Name);
+
+ ulong key = JsonClassInfo.GetKey(Name);
+ PropertyNameKey = key;
}
private void DetermineSerializationCapabilities()
public byte[] Name { get; private set; }
public string NameAsString { get; private set; }
- // Used to support case-insensitive comparison
- public byte[] NameUsedToCompare { get; private set; }
- public string NameUsedToCompareAsString { get; private set; }
+ // Key for fast property name lookup.
+ public ulong PropertyNameKey { get; set; }
// Options can be referenced here since all JsonPropertyInfos originate from a JsonClassInfo that is cached on JsonSerializerOptions.
protected JsonSerializerOptions Options { get; set; }
if (ElementClassInfo != null)
{
// Forward the setter to the value-based JsonPropertyInfo.
- JsonPropertyInfo propertyInfo = ElementClassInfo.GetPolicyProperty();
+ JsonPropertyInfo propertyInfo = ElementClassInfo.PolicyProperty;
propertyInfo.ReadEnumerable(tokenType, ref state, ref reader);
}
else
{
Debug.Assert(ShouldSerialize);
- if (state.Current.Enumerator != null)
+ if (state.Current.CollectionEnumerator != null)
{
// Forward the setter to the value-based JsonPropertyInfo.
- JsonPropertyInfo propertyInfo = ElementClassInfo.GetPolicyProperty();
+ JsonPropertyInfo propertyInfo = ElementClassInfo.PolicyProperty;
propertyInfo.WriteEnumerable(ref state, writer);
}
else
{
if (Converter != null)
{
- Debug.Assert(current.Enumerator != null);
+ Debug.Assert(current.CollectionEnumerator != null);
TConverter value;
- if (current.Enumerator is IEnumerator<TConverter> enumerator)
+ if (current.CollectionEnumerator is IEnumerator<TConverter> enumerator)
{
// Avoid boxing for strongly-typed enumerators such as returned from IList<T>.
value = enumerator.Current;
}
else
{
- value = (TConverter)current.Enumerator.Current;
+ value = (TConverter)current.CollectionEnumerator.Current;
}
if (value == null)
{
if (Converter != null)
{
- Debug.Assert(current.Enumerator != null);
+ Debug.Assert(current.CollectionEnumerator != null);
TConverter value;
- if (current.Enumerator is IEnumerator<TConverter> enumerator)
+ if (current.CollectionEnumerator is IEnumerator<TConverter> enumerator)
{
// Avoid boxing for strongly-typed enumerators such as returned from IList<T>.
value = enumerator.Current;
}
else
{
- value = (TConverter)current.Enumerator.Current;
+ value = (TConverter)current.CollectionEnumerator.Current;
}
if (value == null)
{
if (Converter != null)
{
- Debug.Assert(current.Enumerator != null);
+ Debug.Assert(current.CollectionEnumerator != null);
TProperty? value;
- if (current.Enumerator is IEnumerator<TProperty?> enumerator)
+ if (current.CollectionEnumerator is IEnumerator<TProperty?> enumerator)
{
// Avoid boxing for strongly-typed enumerators such as returned from IList<T>.
value = enumerator.Current;
}
else
{
- value = (TProperty?)current.Enumerator.Current;
+ value = (TProperty?)current.CollectionEnumerator.Current;
}
if (value == null)
if (state.Current.IsDictionary || state.Current.IsIDictionaryConstructible)
{
- state.Current.JsonPropertyInfo = state.Current.JsonClassInfo.GetPolicyProperty();
+ state.Current.JsonPropertyInfo = state.Current.JsonClassInfo.PolicyProperty;
}
Debug.Assert(
propertyName = GetUnescapedString(propertyName, idx);
}
- state.Current.JsonPropertyInfo = state.Current.JsonClassInfo.GetProperty(options, propertyName, ref state.Current);
+ state.Current.JsonPropertyInfo = state.Current.JsonClassInfo.GetProperty(propertyName, ref state.Current);
if (state.Current.JsonPropertyInfo == null)
{
if (state.Current.JsonClassInfo.DataExtensionProperty == null)
readStack.Push();
readStack.Current.Drain = true;
}
- else if (readStack.Current.IsProcessingValue)
+ else if (readStack.Current.IsProcessingValue(tokenType))
{
if (!HandleObjectAsValue(tokenType, options, ref reader, ref readStack, ref initialState))
{
}
else if (tokenType == JsonTokenType.StartArray)
{
- if (!readStack.Current.IsProcessingValue)
+ if (!readStack.Current.IsProcessingValue(tokenType))
{
HandleStartArray(options, ref reader, ref readStack);
}
ref WriteStack state)
{
JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo;
- if (state.Current.Enumerator == null)
+ if (state.Current.CollectionEnumerator == null)
{
IEnumerable enumerable;
if (enumerable is IDictionary dictionary)
{
- state.Current.Enumerator = dictionary.GetEnumerator();
+ state.Current.CollectionEnumerator = dictionary.GetEnumerator();
}
else
{
- state.Current.Enumerator = enumerable.GetEnumerator();
+ state.Current.CollectionEnumerator = enumerable.GetEnumerator();
}
state.Current.WriteObjectOrArrayStart(ClassType.Dictionary, writer);
}
- if (state.Current.Enumerator.MoveNext())
+ if (state.Current.CollectionEnumerator.MoveNext())
{
// Check for polymorphism.
if (elementClassInfo.ClassType == ClassType.Unknown)
{
- object currentValue = ((IDictionaryEnumerator)state.Current.Enumerator).Entry.Value;
+ object currentValue = ((IDictionaryEnumerator)state.Current.CollectionEnumerator).Entry.Value;
GetRuntimeClassInfo(currentValue, ref elementClassInfo, options);
}
if (elementClassInfo.ClassType == ClassType.Value)
{
- elementClassInfo.GetPolicyProperty().WriteDictionary(ref state, writer);
+ elementClassInfo.PolicyProperty.WriteDictionary(ref state, writer);
}
- else if (state.Current.Enumerator.Current == null)
+ else if (state.Current.CollectionEnumerator.Current == null)
{
writer.WriteNull(jsonPropertyInfo.Name);
}
else
{
// An object or another enumerator requires a new stack frame.
- var enumerator = (IDictionaryEnumerator)state.Current.Enumerator;
+ var enumerator = (IDictionaryEnumerator)state.Current.CollectionEnumerator;
object value = enumerator.Value;
state.Push(elementClassInfo, value);
state.Current.KeyName = (string)enumerator.Key;
// We are done enumerating.
writer.WriteEndObject();
- if (state.Current.PopStackOnEnd)
+ if (state.Current.PopStackOnEndCollection)
{
state.Pop();
}
return;
}
- Debug.Assert(current.Enumerator != null);
+ Debug.Assert(current.CollectionEnumerator != null);
string key;
TProperty value;
- if (current.Enumerator is IEnumerator<KeyValuePair<string, TProperty>> enumerator)
+ if (current.CollectionEnumerator is IEnumerator<KeyValuePair<string, TProperty>> enumerator)
{
// Avoid boxing for strongly-typed enumerators such as returned from IDictionary<string, TRuntimeProperty>
value = enumerator.Current.Value;
key = enumerator.Current.Key;
}
- else if (current.Enumerator is IEnumerator<KeyValuePair<string, object>> polymorphicEnumerator)
+ else if (current.CollectionEnumerator is IEnumerator<KeyValuePair<string, object>> polymorphicEnumerator)
{
value = (TProperty)polymorphicEnumerator.Current.Value;
key = polymorphicEnumerator.Current.Key;
}
else if (current.IsIDictionaryConstructible || current.IsIDictionaryConstructibleProperty)
{
- value = (TProperty)((DictionaryEntry)current.Enumerator.Current).Value;
- key = (string)((DictionaryEntry)current.Enumerator.Current).Key;
+ value = (TProperty)((DictionaryEntry)current.CollectionEnumerator.Current).Value;
+ key = (string)((DictionaryEntry)current.CollectionEnumerator.Current).Key;
}
else
{
{
Debug.Assert(state.Current.JsonPropertyInfo.ClassType == ClassType.Enumerable);
- if (state.Current.Enumerator == null)
+ if (state.Current.CollectionEnumerator == null)
{
IEnumerable enumerable = (IEnumerable)state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.CurrentValue);
return true;
}
- state.Current.Enumerator = enumerable.GetEnumerator();
+ state.Current.CollectionEnumerator = enumerable.GetEnumerator();
state.Current.WriteObjectOrArrayStart(ClassType.Enumerable, writer);
}
- if (state.Current.Enumerator.MoveNext())
+ if (state.Current.CollectionEnumerator.MoveNext())
{
// Check for polymorphism.
if (elementClassInfo.ClassType == ClassType.Unknown)
{
- object currentValue = state.Current.Enumerator.Current;
+ object currentValue = state.Current.CollectionEnumerator.Current;
GetRuntimeClassInfo(currentValue, ref elementClassInfo, options);
}
if (elementClassInfo.ClassType == ClassType.Value)
{
- elementClassInfo.GetPolicyProperty().WriteEnumerable(ref state, writer);
+ elementClassInfo.PolicyProperty.WriteEnumerable(ref state, writer);
}
- else if (state.Current.Enumerator.Current == null)
+ else if (state.Current.CollectionEnumerator.Current == null)
{
// Write a null object or enumerable.
writer.WriteNullValue();
else
{
// An object or another enumerator requires a new stack frame.
- object nextValue = state.Current.Enumerator.Current;
+ object nextValue = state.Current.CollectionEnumerator.Current;
state.Push(elementClassInfo, nextValue);
}
// We are done enumerating.
writer.WriteEndArray();
- if (state.Current.PopStackOnEnd)
+ if (state.Current.PopStackOnEndCollection)
{
state.Pop();
}
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System.Collections.Generic;
using System.Diagnostics;
namespace System.Text.Json
if (!state.Current.StartObjectWritten)
{
state.Current.WriteObjectOrArrayStart(ClassType.Object, writer);
+ state.Current.PropertyEnumerator = state.Current.JsonClassInfo.PropertyCache.GetEnumerator();
+ state.Current.NextProperty();
}
-
- if (state.Current.MoveToNextProperty)
+ else if (state.Current.MoveToNextProperty)
{
state.Current.NextProperty();
}
// Determine if we are done enumerating properties.
- // If the ClassType is unknown, there will be a policy property applied. There is probably
- // a better way to identify policy properties- maybe not put them in the normal property bag?
+ // If the ClassType is unknown, there will be a policy property applied
JsonClassInfo classInfo = state.Current.JsonClassInfo;
- if (classInfo.ClassType != ClassType.Unknown && state.Current.PropertyIndex != classInfo.PropertyCount)
+ if (classInfo.ClassType != ClassType.Unknown && state.Current.PropertyEnumeratorActive)
{
HandleObject(options, writer, ref state);
return false;
state.Current.JsonClassInfo.ClassType == ClassType.Object ||
state.Current.JsonClassInfo.ClassType == ClassType.Unknown);
- JsonPropertyInfo jsonPropertyInfo = state.Current.JsonClassInfo.GetProperty(state.Current.PropertyIndex);
+ var kvp = (KeyValuePair<string, JsonPropertyInfo>)state.Current.PropertyEnumerator.Current;
+ JsonPropertyInfo jsonPropertyInfo = kvp.Value;
if (!jsonPropertyInfo.ShouldSerialize)
{
state.Current.MoveToNextProperty = true;
// For performance, we order the properties by the first deserialize and PropertyIndex helps find the right slot quicker.
public int PropertyIndex;
- public HashSet<PropertyRef> PropertyRefCache;
+ public List<PropertyRef> PropertyRefCache;
// The current JSON data for a property does not match a given POCO, so ignore the property (recursively).
public bool Drain;
public bool IsProcessingIDictionaryConstructible => IsIDictionaryConstructible || IsIDictionaryConstructibleProperty;
public bool IsProcessingEnumerable => IsEnumerable || IsEnumerableProperty;
- public bool IsProcessingValue
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool IsProcessingValue(JsonTokenType tokenType)
{
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- get
+ if (SkipProperty)
{
- if (SkipProperty)
- {
- return false;
- }
+ return false;
+ }
- if (CollectionPropertyInitialized)
- {
- ClassType elementType;
-
- if (IsCollectionForProperty)
- {
- elementType = JsonPropertyInfo.ElementClassInfo.ClassType;
- }
- else
- {
- Debug.Assert(IsCollectionForClass);
- elementType = JsonClassInfo.ElementClassInfo.ClassType;
- }
+ // Handle array case.
+ if (CollectionPropertyInitialized)
+ {
+ ClassType elementType;
+ if (IsCollectionForClass)
+ {
+ // A custom converter for a class (to handle JSON array).
+ elementType = JsonClassInfo.ElementClassInfo.ClassType;
return (elementType == ClassType.Value || elementType == ClassType.Unknown);
}
- ClassType type;
- if (JsonPropertyInfo == null)
+ Debug.Assert(IsCollectionForProperty);
+
+ if (tokenType == JsonTokenType.StartObject)
{
- type = JsonClassInfo.ClassType;
+ elementType = JsonPropertyInfo.ElementClassInfo.ClassType;
+ return (elementType == ClassType.Value || elementType == ClassType.Unknown);
}
else
{
- type = JsonPropertyInfo.ClassType;
+ // A custom converter for an array element is handled by IsProcessingValueOnStartObject.
+ return false;
}
+ }
- // If we're a Value or polymorphic Value (ClassType.Unknown), return true.
- return type == ClassType.Value || type == ClassType.Unknown;
+ // Handle object case.
+ ClassType type;
+ if (JsonPropertyInfo == null)
+ {
+ type = JsonClassInfo.ClassType;
}
+ else
+ {
+ type = JsonPropertyInfo.ClassType;
+ }
+
+ return type == ClassType.Value || type == ClassType.Unknown;
}
public void Initialize(Type type, JsonSerializerOptions options)
JsonClassInfo.ClassType == ClassType.Dictionary ||
JsonClassInfo.ClassType == ClassType.IDictionaryConstructible)
{
- JsonPropertyInfo = JsonClassInfo.GetPolicyProperty();
+ JsonPropertyInfo = JsonClassInfo.PolicyProperty;
}
}
IList converterList;
if (jsonPropertyInfo.ElementClassInfo.ClassType == ClassType.Value)
{
- converterList = jsonPropertyInfo.ElementClassInfo.GetPolicyProperty().CreateConverterList();
+ converterList = jsonPropertyInfo.ElementClassInfo.PolicyProperty.CreateConverterList();
}
else
{
if (classType == ClassType.Enumerable || nextClassInfo.ClassType == ClassType.Dictionary)
{
- Current.PopStackOnEnd = true;
- Current.JsonPropertyInfo = Current.JsonClassInfo.GetPolicyProperty();
+ Current.PopStackOnEndCollection = true;
+ Current.JsonPropertyInfo = Current.JsonClassInfo.PolicyProperty;
}
else if (classType == ClassType.IDictionaryConstructible)
{
- Current.PopStackOnEnd = true;
- Current.JsonPropertyInfo = Current.JsonClassInfo.GetPolicyProperty();
+ Current.PopStackOnEndCollection = true;
+ Current.JsonPropertyInfo = Current.JsonClassInfo.PolicyProperty;
Current.IsIDictionaryConstructible = true;
}
// Support Dictionary keys.
public string KeyName;
- // Whether the current object can be constructed with IDictionary.
+ // The current IEnumerable or IDictionary.
+ public IEnumerator CollectionEnumerator;
+ public bool PopStackOnEndCollection;
public bool IsIDictionaryConstructible;
public bool IsIDictionaryConstructibleProperty;
- // The current enumerator for the IEnumerable or IDictionary.
- public IEnumerator Enumerator;
-
- // Current property values.
- public JsonPropertyInfo JsonPropertyInfo;
-
- // The current property.
- public int PropertyIndex;
- public bool MoveToNextProperty;
-
- // Has the Start tag been written.
+ // The current object.
+ public bool PopStackOnEndObject;
public bool StartObjectWritten;
+ public bool MoveToNextProperty;
- // Pop the stack when the current array or dictionary is done.
- public bool PopStackOnEnd;
-
- // Pop the stack when the current object is done.
- public bool PopStackOnEndObject;
+ // The current property.
+ public IEnumerator PropertyEnumerator;
+ public bool PropertyEnumeratorActive;
+ public JsonPropertyInfo JsonPropertyInfo;
public void Initialize(Type type, JsonSerializerOptions options)
{
JsonClassInfo = options.GetOrAddClass(type);
if (JsonClassInfo.ClassType == ClassType.Value || JsonClassInfo.ClassType == ClassType.Enumerable || JsonClassInfo.ClassType == ClassType.Dictionary)
{
- JsonPropertyInfo = JsonClassInfo.GetPolicyProperty();
+ JsonPropertyInfo = JsonClassInfo.PolicyProperty;
}
else if (JsonClassInfo.ClassType == ClassType.IDictionaryConstructible)
{
- JsonPropertyInfo = JsonClassInfo.GetPolicyProperty();
+ JsonPropertyInfo = JsonClassInfo.PolicyProperty;
IsIDictionaryConstructible = true;
}
}
public void Reset()
{
CurrentValue = null;
- Enumerator = null;
+ CollectionEnumerator = null;
KeyName = null;
JsonClassInfo = null;
JsonPropertyInfo = null;
- PropertyIndex = 0;
IsIDictionaryConstructible = false;
MoveToNextProperty = false;
PopStackOnEndObject = false;
- PopStackOnEnd = false;
+ PopStackOnEndCollection = false;
StartObjectWritten = false;
}
public void EndObject()
{
- PropertyIndex = 0;
- MoveToNextProperty = false;
- PopStackOnEndObject = false;
IsIDictionaryConstructibleProperty = false;
JsonPropertyInfo = null;
+ MoveToNextProperty = false;
+ PopStackOnEndObject = false;
+ PropertyEnumerator = null;
+ PropertyEnumeratorActive = false;
}
public void EndDictionary()
{
- Enumerator = null;
- PopStackOnEnd = false;
+ CollectionEnumerator = null;
+ PopStackOnEndCollection = false;
}
public void EndArray()
{
- Enumerator = null;
- PopStackOnEnd = false;
+ CollectionEnumerator = null;
+ PopStackOnEndCollection = false;
JsonPropertyInfo = null;
}
{
JsonPropertyInfo = null;
MoveToNextProperty = false;
- PropertyIndex++;
+ PropertyEnumeratorActive = PropertyEnumerator.MoveNext();
}
}
}
}
/// <summary>
- /// A converter that converters True and False to a bool.
+ /// A converter that converts "true" and "false" tokens to a bool.
/// </summary>
private class ObjectToBoolConverter : JsonConverter<object>
{