private static JsonPropertyInfo?GetPropertyWithUniqueAttribute(Type classType, Type attributeType, Dictionary <string, JsonPropertyInfo> cache) { JsonPropertyInfo?property = null; foreach (JsonPropertyInfo jsonPropertyInfo in cache.Values) { Debug.Assert(jsonPropertyInfo.PropertyInfo != null); Attribute?attribute = jsonPropertyInfo.PropertyInfo.GetCustomAttribute(attributeType); if (attribute != null) { if (property != null) { ThrowHelper.ThrowInvalidOperationException_SerializationDuplicateTypeAttribute(classType, attributeType); } property = jsonPropertyInfo; } } return(property); }
private void DeterminePropertyName() { if (PropertyInfo == null) { return; } JsonPropertyNameAttribute?nameAttribute = GetAttribute <JsonPropertyNameAttribute>(PropertyInfo); if (nameAttribute != null) { string name = nameAttribute.Name; if (name == null) { ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameNull(ParentClassType, this); } NameAsString = name; } else if (Options.PropertyNamingPolicy != null) { string name = Options.PropertyNamingPolicy.ConvertName(PropertyInfo.Name); if (name == null) { ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameNull(ParentClassType, this); } NameAsString = name; } else { NameAsString = PropertyInfo.Name; } Debug.Assert(NameAsString != null); NameAsUtf8Bytes = Encoding.UTF8.GetBytes(NameAsString); EscapedNameSection = JsonHelpers.GetEscapedPropertyNameSection(NameAsUtf8Bytes, Options.Encoder); }
public void Write(ref WriteStack state, Utf8JsonWriter writer) { Debug.Assert(ShouldSerialize); if (state.Current.CollectionEnumerator != null) { // Forward the setter to the value-based JsonPropertyInfo. JsonPropertyInfo propertyInfo = ElementClassInfo.PolicyProperty; propertyInfo.WriteEnumerable(ref state, writer); } else { int originalDepth = writer.CurrentDepth; OnWrite(ref state.Current, writer); if (originalDepth != writer.CurrentDepth) { ThrowHelper.ThrowJsonException_SerializationConverterWrite(state.PropertyPath, ConverterBase.ToString()); } } }
private JsonPropertyInfo GetPropertyThatHasAttribute(Type attributeType) { Debug.Assert(PropertyCache != null); JsonPropertyInfo property = null; foreach (JsonPropertyInfo jsonPropertyInfo in PropertyCache.Values) { Attribute attribute = jsonPropertyInfo.PropertyInfo.GetCustomAttribute(attributeType); if (attribute != null) { if (property != null) { ThrowHelper.ThrowInvalidOperationException_SerializationDuplicateTypeAttribute(Type, attributeType); } property = jsonPropertyInfo; } } return(property); }
public static object CreateDictionaryValue(ref ReadStack state) { JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo; // If the property has a DictionaryConverter, then we use tempDictionaryValues. if (jsonPropertyInfo.DictionaryConverter != null) { IDictionary converterDictionary; JsonClassInfo elementClassInfo = jsonPropertyInfo.ElementClassInfo; if (elementClassInfo.ClassType == ClassType.Value) { converterDictionary = elementClassInfo.PolicyProperty.CreateConverterDictionary(); } else { converterDictionary = new Dictionary <string, object>(); } state.Current.TempDictionaryValues = converterDictionary; // Clear the value if present to ensure we don't confuse TempDictionaryValues with the collection. if (!jsonPropertyInfo.IsPropertyPolicy && jsonPropertyInfo.CanBeNull) { jsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, null); } return(null); } JsonClassInfo runtimeClassInfo = jsonPropertyInfo.RuntimeClassInfo; if (runtimeClassInfo.CreateObject == null) { ThrowHelper.ThrowNotSupportedException_DeserializeCreateObjectDelegateIsNull(jsonPropertyInfo.DeclaredPropertyType); } return(runtimeClassInfo.CreateObject()); }
public static object CreateEnumerableValue(ref Utf8JsonReader reader, ref ReadStack state, JsonSerializerOptions options) { JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo; // If the property has an EnumerableConverter, then we use tempEnumerableValues. if (jsonPropertyInfo.EnumerableConverter != null) { IList converterList; if (jsonPropertyInfo.ElementClassInfo.ClassType == ClassType.Value) { converterList = jsonPropertyInfo.ElementClassInfo.GetPolicyProperty().CreateConverterList(); } else { converterList = new List <object>(); } state.Current.TempEnumerableValues = converterList; return(null); } Type propType = state.Current.JsonPropertyInfo.RuntimePropertyType; if (typeof(IList).IsAssignableFrom(propType)) { // If IList, add the members as we create them. JsonClassInfo collectionClassInfo = state.Current.JsonPropertyInfo.RuntimeClassInfo; IList collection = (IList)collectionClassInfo.CreateObject(); return(collection); } else { ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(propType, reader, state.JsonPath); return(null); } }
protected override void OnReadEnumerable(ref ReadStack state, ref Utf8JsonReader reader) { if (Converter == null) { ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType); } if (state.Current.KeyName == null && state.Current.IsProcessingDictionaryOrIDictionaryConstructible()) { ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType); return; } // We need an initialized array in order to store the values. if (state.Current.IsProcessingEnumerable() && state.Current.TempEnumerableValues == null && state.Current.ReturnValue == null) { ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType); return; } TConverter value = Converter.Read(ref reader, RuntimePropertyType, Options); JsonSerializer.ApplyValueToEnumerable(ref value, ref state); }
public void DetermineEnumerablePopulationStrategy(object targetEnumerable) { Debug.Assert(JsonPropertyInfo.ClassType == ClassType.Enumerable); if (JsonPropertyInfo.RuntimeClassInfo.AddItemToObject != null) { if (!JsonPropertyInfo.TryCreateEnumerableAddMethod(targetEnumerable, out object addMethodDelegate)) { // No "add" method for this collection, hence, not supported for deserialization. throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection( JsonPropertyInfo.DeclaredPropertyType, JsonPropertyInfo.ParentClassType, JsonPropertyInfo.PropertyInfo); } AddObjectToEnumerable = addMethodDelegate; } else if (targetEnumerable is IList targetList) { if (targetList.IsReadOnly) { throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection( JsonPropertyInfo.DeclaredPropertyType, JsonPropertyInfo.ParentClassType, JsonPropertyInfo.PropertyInfo); } } // If there's no add method, and we can't cast to IList, this collection is not supported for deserialization. else { throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection( JsonPropertyInfo.DeclaredPropertyType, JsonPropertyInfo.ParentClassType, JsonPropertyInfo.PropertyInfo); } }
public bool DetermineExtensionDataProperty(Dictionary <string, JsonPropertyInfo> cache) { JsonPropertyInfo?jsonPropertyInfo = GetPropertyWithUniqueAttribute(Type, typeof(JsonExtensionDataAttribute), cache); if (jsonPropertyInfo != null) { Type declaredPropertyType = jsonPropertyInfo.DeclaredPropertyType; if (typeof(IDictionary <string, object>).IsAssignableFrom(declaredPropertyType) || typeof(IDictionary <string, JsonElement>).IsAssignableFrom(declaredPropertyType)) { JsonConverter converter = Options.GetConverter(declaredPropertyType); Debug.Assert(converter != null); } else { ThrowHelper.ThrowInvalidOperationException_SerializationDataExtensionPropertyInvalid(Type, jsonPropertyInfo); } DataExtensionProperty = jsonPropertyInfo; return(true); } return(false); }
private static TValue?Read <TValue>(ref Utf8JsonReader reader, JsonTypeInfo jsonTypeInfo) { ReadStack state = default; state.Initialize(jsonTypeInfo); JsonReaderState readerState = reader.CurrentState; if (readerState.Options.CommentHandling == JsonCommentHandling.Allow) { throw new ArgumentException(SR.JsonSerializerDoesNotSupportComments, nameof(reader)); } // Value copy to overwrite the ref on an exception and undo the destructive reads. Utf8JsonReader restore = reader; ReadOnlySpan <byte> valueSpan = default; ReadOnlySequence <byte> valueSequence = default; try { switch (reader.TokenType) { // A new reader was created and has never been read, // so we need to move to the first token. // (or a reader has terminated and we're about to throw) case JsonTokenType.None: // Using a reader loop the caller has identified a property they wish to // hydrate into a JsonDocument. Move to the value first. case JsonTokenType.PropertyName: { if (!reader.Read()) { ThrowHelper.ThrowJsonReaderException(ref reader, ExceptionResource.ExpectedOneCompleteToken); } break; } } switch (reader.TokenType) { // Any of the "value start" states are acceptable. case JsonTokenType.StartObject: case JsonTokenType.StartArray: { long startingOffset = reader.TokenStartIndex; if (!reader.TrySkip()) { ThrowHelper.ThrowJsonReaderException(ref reader, ExceptionResource.NotEnoughData); } long totalLength = reader.BytesConsumed - startingOffset; ReadOnlySequence <byte> sequence = reader.OriginalSequence; if (sequence.IsEmpty) { valueSpan = reader.OriginalSpan.Slice( checked ((int)startingOffset), checked ((int)totalLength)); } else { valueSequence = sequence.Slice(startingOffset, totalLength); } Debug.Assert( reader.TokenType == JsonTokenType.EndObject || reader.TokenType == JsonTokenType.EndArray); break; } // Single-token values case JsonTokenType.Number: case JsonTokenType.True: case JsonTokenType.False: case JsonTokenType.Null: { if (reader.HasValueSequence) { valueSequence = reader.ValueSequence; } else { valueSpan = reader.ValueSpan; } break; } // String's ValueSequence/ValueSpan omits the quotes, we need them back. case JsonTokenType.String: { ReadOnlySequence <byte> sequence = reader.OriginalSequence; if (sequence.IsEmpty) { // Since the quoted string fit in a ReadOnlySpan originally // the contents length plus the two quotes can't overflow. int payloadLength = reader.ValueSpan.Length + 2; Debug.Assert(payloadLength > 1); ReadOnlySpan <byte> readerSpan = reader.OriginalSpan; Debug.Assert( readerSpan[(int)reader.TokenStartIndex] == (byte)'"', $"Calculated span starts with {readerSpan[(int)reader.TokenStartIndex]}"); Debug.Assert( readerSpan[(int)reader.TokenStartIndex + payloadLength - 1] == (byte)'"', $"Calculated span ends with {readerSpan[(int)reader.TokenStartIndex + payloadLength - 1]}"); valueSpan = readerSpan.Slice((int)reader.TokenStartIndex, payloadLength); } else { long payloadLength = 2; if (reader.HasValueSequence) { payloadLength += reader.ValueSequence.Length; } else { payloadLength += reader.ValueSpan.Length; } valueSequence = sequence.Slice(reader.TokenStartIndex, payloadLength); Debug.Assert( valueSequence.First.Span[0] == (byte)'"', $"Calculated sequence starts with {valueSequence.First.Span[0]}"); Debug.Assert( valueSequence.ToArray()[payloadLength - 1] == (byte)'"', $"Calculated sequence ends with {valueSequence.ToArray()[payloadLength - 1]}"); } break; } default: { byte displayByte; if (reader.HasValueSequence) { displayByte = reader.ValueSequence.First.Span[0]; } else { displayByte = reader.ValueSpan[0]; } ThrowHelper.ThrowJsonReaderException( ref reader, ExceptionResource.ExpectedStartOfValueNotFound, displayByte); break; } } } catch (JsonReaderException ex) { reader = restore; // Re-throw with Path information. ThrowHelper.ReThrowWithPath(ref state, ex); } int length = valueSpan.IsEmpty ? checked ((int)valueSequence.Length) : valueSpan.Length; byte[] rented = ArrayPool <byte> .Shared.Rent(length); Span <byte> rentedSpan = rented.AsSpan(0, length); try { if (valueSpan.IsEmpty) { valueSequence.CopyTo(rentedSpan); } else { valueSpan.CopyTo(rentedSpan); } JsonReaderOptions originalReaderOptions = readerState.Options; var newReader = new Utf8JsonReader(rentedSpan, originalReaderOptions); JsonConverter jsonConverter = state.Current.JsonPropertyInfo !.ConverterBase; TValue? value = ReadCore <TValue>(jsonConverter, ref newReader, jsonTypeInfo.Options, ref state); // The reader should have thrown if we have remaining bytes. Debug.Assert(newReader.BytesConsumed == length); return(value); } catch (JsonException) { reader = restore; throw; } finally { rentedSpan.Clear(); ArrayPool <byte> .Shared.Return(rented); } }
public JsonClassInfo(Type type, JsonSerializerOptions options) { Type = type; Options = options; ClassType = GetClassType(type, options); CreateObject = options.MemberAccessorStrategy.CreateConstructor(type); // Ignore properties on enumerable. switch (ClassType) { case ClassType.Object: { PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); Dictionary <string, JsonPropertyInfo> cache = CreatePropertyCache(properties.Length); foreach (PropertyInfo propertyInfo in properties) { // Ignore indexers if (propertyInfo.GetIndexParameters().Length > 0) { continue; } // For now we only support public getters\setters if (propertyInfo.GetMethod?.IsPublic == true || propertyInfo.SetMethod?.IsPublic == true) { JsonPropertyInfo jsonPropertyInfo = AddProperty(propertyInfo.PropertyType, propertyInfo, type, options); Debug.Assert(jsonPropertyInfo != null); // If the JsonPropertyNameAttribute or naming policy results in collisions, throw an exception. if (!JsonHelpers.TryAdd(cache, jsonPropertyInfo.NameAsString, jsonPropertyInfo)) { JsonPropertyInfo other = cache[jsonPropertyInfo.NameAsString]; if (other.ShouldDeserialize == false && other.ShouldSerialize == false) { // Overwrite the one just added since it has [JsonIgnore]. cache[jsonPropertyInfo.NameAsString] = jsonPropertyInfo; } else if (jsonPropertyInfo.ShouldDeserialize == true || jsonPropertyInfo.ShouldSerialize == true) { ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameConflict(this, jsonPropertyInfo); } // else ignore jsonPropertyInfo since it has [JsonIgnore]. } } } if (DetermineExtensionDataProperty(cache)) { // Remove from cache since it is handled independently. cache.Remove(DataExtensionProperty.NameAsString); } // Set as a unit to avoid concurrency issues. PropertyCache = cache; } break; case ClassType.Enumerable: case ClassType.Dictionary: { // Add a single property that maps to the class type so we can have policies applied. AddPolicyProperty(type, options); Type objectType; if (IsNativelySupportedCollection(type)) { // Use the type from the property policy to get any late-bound concrete types (from an interface like IDictionary). objectType = PolicyProperty.RuntimePropertyType; } else { // We need to create the declared instance for types implementing natively supported collections. objectType = PolicyProperty.DeclaredPropertyType; } CreateObject = options.MemberAccessorStrategy.CreateConstructor(objectType); ElementType = GetElementType(type, parentType: null, memberInfo: null, options: options); } break; case ClassType.IDictionaryConstructible: { // Add a single property that maps to the class type so we can have policies applied. AddPolicyProperty(type, options); ElementType = GetElementType(type, parentType: null, memberInfo: null, options: options); CreateConcreteDictionary = options.MemberAccessorStrategy.CreateConstructor( typeof(Dictionary <,>).MakeGenericType(typeof(string), ElementType)); CreateObject = options.MemberAccessorStrategy.CreateConstructor(PolicyProperty.DeclaredPropertyType); } 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}"); break; } }
private static void ReadCore( JsonSerializerOptions options, ref Utf8JsonReader reader, ref ReadStack readStack) { try { JsonReaderState initialState = default; long initialBytesConsumed = default; while (true) { if (readStack.ReadAhead) { // When we're reading ahead we always have to save the state // as we don't know if the next token is an opening object or // array brace. initialState = reader.CurrentState; initialBytesConsumed = reader.BytesConsumed; } if (!reader.Read()) { // Need more data break; } JsonTokenType tokenType = reader.TokenType; if (JsonHelpers.IsInRangeInclusive(tokenType, JsonTokenType.String, JsonTokenType.False)) { Debug.Assert(tokenType == JsonTokenType.String || tokenType == JsonTokenType.Number || tokenType == JsonTokenType.True || tokenType == JsonTokenType.False); HandleValue(tokenType, options, ref reader, ref readStack); } else if (tokenType == JsonTokenType.PropertyName) { HandlePropertyName(options, ref reader, ref readStack); } else if (tokenType == JsonTokenType.StartObject) { if (readStack.Current.SkipProperty) { readStack.Push(); readStack.Current.Drain = true; } else if (readStack.Current.IsProcessingValue()) { if (!HandleObjectAsValue(tokenType, options, ref reader, ref readStack, ref initialState, initialBytesConsumed)) { // Need more data break; } } else if (readStack.Current.IsProcessingDictionary()) { HandleStartDictionary(options, ref readStack); } else { HandleStartObject(options, ref readStack); } } else if (tokenType == JsonTokenType.EndObject) { if (readStack.Current.Drain) { readStack.Pop(); // Clear the current property in case it is a dictionary, since dictionaries must have EndProperty() called when completed. // A non-dictionary property can also have EndProperty() called when completed, although it is redundant. readStack.Current.EndProperty(); } else if (readStack.Current.IsProcessingDictionary()) { HandleEndDictionary(options, ref readStack); } else { HandleEndObject(ref readStack); } } else if (tokenType == JsonTokenType.StartArray) { if (!readStack.Current.IsProcessingValue()) { HandleStartArray(options, ref readStack); } else if (!HandleObjectAsValue(tokenType, options, ref reader, ref readStack, ref initialState, initialBytesConsumed)) { // Need more data break; } } else if (tokenType == JsonTokenType.EndArray) { HandleEndArray(options, ref readStack); } else if (tokenType == JsonTokenType.Null) { HandleNull(options, ref reader, ref readStack); } } } catch (JsonReaderException ex) { // Re-throw with Path information. ThrowHelper.ReThrowWithPath(readStack, ex); } catch (FormatException ex) when(ex.Source == ThrowHelper.ExceptionSourceValueToRethrowAsJsonException) { ThrowHelper.ReThrowWithPath(readStack, reader, ex); } catch (InvalidOperationException ex) when(ex.Source == ThrowHelper.ExceptionSourceValueToRethrowAsJsonException) { ThrowHelper.ReThrowWithPath(readStack, reader, ex); } catch (JsonException ex) { ThrowHelper.AddExceptionInformation(readStack, reader, ex); throw; } readStack.BytesConsumed += reader.BytesConsumed; }
public JsonClassInfo(Type type, JsonSerializerOptions options) { Type = type; Options = options; JsonConverter converter = GetConverter( Type, parentClassType: null, // A ClassInfo never has a "parent" class. propertyInfo: null, // A ClassInfo never has a "parent" property. out Type runtimeType, Options); ClassType = converter.ClassType; PropertyInfoForClassInfo = CreatePropertyInfoForClassInfo(Type, runtimeType, converter, Options); switch (ClassType) { case ClassType.Object: { // Create the policy property. PropertyInfoForClassInfo = CreatePropertyInfoForClassInfo(type, runtimeType, converter !, options); CreateObject = options.MemberAccessorStrategy.CreateConstructor(type); PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public); Dictionary <string, JsonPropertyInfo> cache = CreatePropertyCache(properties.Length); foreach (PropertyInfo propertyInfo in properties) { // Ignore indexers if (propertyInfo.GetIndexParameters().Length > 0) { continue; } // For now we only support public getters\setters if (propertyInfo.GetMethod?.IsPublic == true || propertyInfo.SetMethod?.IsPublic == true) { JsonPropertyInfo jsonPropertyInfo = AddProperty(propertyInfo.PropertyType, propertyInfo, type, options); Debug.Assert(jsonPropertyInfo != null && jsonPropertyInfo.NameAsString != null); // If the JsonPropertyNameAttribute or naming policy results in collisions, throw an exception. if (!JsonHelpers.TryAdd(cache, jsonPropertyInfo.NameAsString, jsonPropertyInfo)) { JsonPropertyInfo other = cache[jsonPropertyInfo.NameAsString]; if (other.ShouldDeserialize == false && other.ShouldSerialize == false) { // Overwrite the one just added since it has [JsonIgnore]. cache[jsonPropertyInfo.NameAsString] = jsonPropertyInfo; } else if (jsonPropertyInfo.ShouldDeserialize == true || jsonPropertyInfo.ShouldSerialize == true) { ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameConflict(Type, jsonPropertyInfo); } // else ignore jsonPropertyInfo since it has [JsonIgnore]. } } } JsonPropertyInfo[] cacheArray; if (DetermineExtensionDataProperty(cache)) { // Remove from cache since it is handled independently. cache.Remove(DataExtensionProperty !.NameAsString !); cacheArray = new JsonPropertyInfo[cache.Count + 1]; // Set the last element to the extension property. cacheArray[cache.Count] = DataExtensionProperty; } else { cacheArray = new JsonPropertyInfo[cache.Count]; } // Set fields when finished to avoid concurrency issues. PropertyCache = cache; cache.Values.CopyTo(cacheArray, 0); PropertyCacheArray = cacheArray; if (converter.ConstructorIsParameterized) { converter.CreateConstructorDelegate(options); InitializeConstructorParameters(converter.ConstructorInfo); } } break; case ClassType.Enumerable: case ClassType.Dictionary: { ElementType = converter.ElementType; CreateObject = options.MemberAccessorStrategy.CreateConstructor(runtimeType); } break; case ClassType.Value: case ClassType.NewValue: { CreateObject = options.MemberAccessorStrategy.CreateConstructor(type); } break; case ClassType.None: { ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(type); } break; default: Debug.Fail($"Unexpected class type: {ClassType}"); throw new InvalidOperationException(); } }
// There are three conditions to consider for an object (primitive value, enumerable or object) being processed here: // 1) The object type was specified as the root-level return type to a Parse\Read method. // 2) The object is property on a parent object. // 3) The object is an element in an enumerable. private static bool Write( Utf8JsonWriter writer, int originalWriterDepth, int flushThreshold, JsonSerializerOptions options, ref WriteStack state) { bool finishedSerializing; try { do { WriteStackFrame current = state.Current; switch (current.JsonClassInfo.ClassType) { case ClassType.Enumerable: finishedSerializing = HandleEnumerable(current.JsonClassInfo.ElementClassInfo, options, writer, ref state); break; case ClassType.Value: Debug.Assert(current.JsonPropertyInfo.ClassType == ClassType.Value); current.JsonPropertyInfo.Write(ref state, writer); finishedSerializing = true; break; case ClassType.Object: finishedSerializing = WriteObject(options, writer, ref state); break; case ClassType.Dictionary: case ClassType.IDictionaryConstructible: finishedSerializing = HandleDictionary(current.JsonClassInfo.ElementClassInfo, options, writer, ref state); break; default: Debug.Assert(state.Current.JsonClassInfo.ClassType == ClassType.Unknown); // Treat typeof(object) as an empty object. finishedSerializing = WriteObject(options, writer, ref state); break; } if (finishedSerializing) { if (writer.CurrentDepth == 0 || writer.CurrentDepth == originalWriterDepth) { break; } } else if (writer.CurrentDepth >= options.EffectiveMaxDepth) { ThrowHelper.ThrowJsonException_DepthTooLarge(writer.CurrentDepth, state, options); } // If serialization is not yet end and we surpass beyond flush threshold return false and flush stream. if (flushThreshold >= 0 && writer.BytesPending > flushThreshold) { return(false); } } while (true); } catch (InvalidOperationException ex) when(ex.Source == ThrowHelper.ExceptionSourceValueToRethrowAsJsonException) { ThrowHelper.ReThrowWithPath(state, ex); } catch (JsonException ex) { ThrowHelper.AddExceptionInformation(state, ex); throw; } return(true); }
private void InitializeConstructorParameters(ConstructorInfo constructorInfo) { ParameterInfo[] parameters = constructorInfo !.GetParameters(); Dictionary <string, JsonParameterInfo> parameterCache = CreateParameterCache(parameters.Length, Options); Dictionary <string, JsonPropertyInfo> propertyCache = PropertyCache !; foreach (ParameterInfo parameterInfo in parameters) { PropertyInfo?firstMatch = null; bool isBound = false; foreach (JsonPropertyInfo jsonPropertyInfo in PropertyCacheArray !) { // This is not null because it is an actual // property on a type, not a "policy property". PropertyInfo propertyInfo = jsonPropertyInfo.PropertyInfo !; string camelCasePropName = JsonNamingPolicy.CamelCase.ConvertName(propertyInfo.Name); if (parameterInfo.Name == camelCasePropName && parameterInfo.ParameterType == propertyInfo.PropertyType) { if (isBound) { Debug.Assert(firstMatch != null); // Multiple object properties cannot bind to the same // constructor parameter. ThrowHelper.ThrowInvalidOperationException_MultiplePropertiesBindToConstructorParameters( Type, parameterInfo, firstMatch, propertyInfo, constructorInfo); } JsonParameterInfo jsonParameterInfo = AddConstructorParameter(parameterInfo, jsonPropertyInfo, Options); // One object property cannot map to multiple constructor // parameters (ConvertName above can't return multiple strings). parameterCache.Add(jsonParameterInfo.NameAsString, jsonParameterInfo); // Remove property from deserialization cache to reduce the number of JsonPropertyInfos considered during JSON matching. propertyCache.Remove(jsonPropertyInfo.NameAsString !); isBound = true; firstMatch = propertyInfo; } } } // It is invalid for the extension data property to bind with a constructor argument. if (DataExtensionProperty != null && parameterCache.ContainsKey(DataExtensionProperty.NameAsString !)) { ThrowHelper.ThrowInvalidOperationException_ExtensionDataCannotBindToCtorParam(DataExtensionProperty.PropertyInfo !, Type, constructorInfo); } ParameterCache = parameterCache; ParameterCount = parameters.Length; PropertyCache = propertyCache; }
internal static void Unescape(ReadOnlySpan <byte> source, Span <byte> destination, int idx, out int written) { Debug.Assert(idx >= 0 && idx < source.Length); Debug.Assert(source[idx] == JsonConstants.BackSlash); Debug.Assert(destination.Length >= source.Length); source.Slice(0, idx).CopyTo(destination); written = idx; for (; idx < source.Length; idx++) { byte currentByte = source[idx]; if (currentByte == JsonConstants.BackSlash) { idx++; currentByte = source[idx]; if (currentByte == JsonConstants.Quote) { destination[written++] = JsonConstants.Quote; } else if (currentByte == 'n') { destination[written++] = JsonConstants.LineFeed; } else if (currentByte == 'r') { destination[written++] = JsonConstants.CarriageReturn; } else if (currentByte == JsonConstants.BackSlash) { destination[written++] = JsonConstants.BackSlash; } else if (currentByte == JsonConstants.Slash) { destination[written++] = JsonConstants.Slash; } else if (currentByte == 't') { destination[written++] = JsonConstants.Tab; } else if (currentByte == 'b') { destination[written++] = JsonConstants.BackSpace; } else if (currentByte == 'f') { destination[written++] = JsonConstants.FormFeed; } else if (currentByte == 'u') { // The source is known to be valid JSON, and hence if we see a \u, it is guaranteed to have 4 hex digits following it // Otherwise, the Utf8JsonReader would have alreayd thrown an exception. Debug.Assert(source.Length >= idx + 5); bool result = Utf8Parser.TryParse(source.Slice(idx + 1, 4), out int scalar, out int bytesConsumed, 'x'); Debug.Assert(result); Debug.Assert(bytesConsumed == 4); idx += bytesConsumed; // The loop iteration will increment idx past the last hex digit if (JsonHelpers.IsInRangeInclusive((uint)scalar, JsonConstants.HighSurrogateStartValue, JsonConstants.LowSurrogateEndValue)) { // The first hex value cannot be a low surrogate. if (scalar >= JsonConstants.LowSurrogateStartValue) { ThrowHelper.ThrowInvalidOperationException_ReadInvalidUTF16(scalar); } Debug.Assert(JsonHelpers.IsInRangeInclusive((uint)scalar, JsonConstants.HighSurrogateStartValue, JsonConstants.HighSurrogateEndValue)); idx += 3; // Skip the last hex digit and the next \u // We must have a low surrogate following a high surrogate. if (source.Length < idx + 4 || source[idx - 2] != '\\' || source[idx - 1] != 'u') { ThrowHelper.ThrowInvalidOperationException_ReadInvalidUTF16(); } // The source is known to be valid JSON, and hence if we see a \u, it is guaranteed to have 4 hex digits following it // Otherwise, the Utf8JsonReader would have alreayd thrown an exception. result = Utf8Parser.TryParse(source.Slice(idx, 4), out int lowSurrogate, out bytesConsumed, 'x'); Debug.Assert(result); Debug.Assert(bytesConsumed == 4); // If the first hex value is a high surrogate, the next one must be a low surrogate. if (!JsonHelpers.IsInRangeInclusive((uint)lowSurrogate, JsonConstants.LowSurrogateStartValue, JsonConstants.LowSurrogateEndValue)) { ThrowHelper.ThrowInvalidOperationException_ReadInvalidUTF16(lowSurrogate); } idx += bytesConsumed - 1; // The loop iteration will increment idx past the last hex digit // To find the unicode scalar: // (0x400 * (High surrogate - 0xD800)) + Low surrogate - 0xDC00 + 0x10000 scalar = (JsonConstants.BitShiftBy10 * (scalar - JsonConstants.HighSurrogateStartValue)) + (lowSurrogate - JsonConstants.LowSurrogateStartValue) + JsonConstants.UnicodePlane01StartValue; } #if BUILDING_INBOX_LIBRARY var rune = new Rune(scalar); int bytesWritten = rune.EncodeToUtf8(destination.Slice(written)); #else EncodeToUtf8Bytes((uint)scalar, destination.Slice(written), out int bytesWritten); #endif Debug.Assert(bytesWritten <= 4); written += bytesWritten; } } else { destination[written++] = currentByte; } } }
// If this method is changed, also change ApplyValueToEnumerable. internal static void ApplyObjectToEnumerable( object value, ref ReadStack state, ref Utf8JsonReader reader, bool setPropertyDirectly = false) { Debug.Assert(!state.Current.SkipProperty); if (state.Current.IsEnumerable) { if (state.Current.TempEnumerableValues != null) { state.Current.TempEnumerableValues.Add(value); } else { if (!(state.Current.ReturnValue is IList list)) { ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(value.GetType(), reader, state.JsonPath); return; } list.Add(value); } } else if (!setPropertyDirectly && state.Current.IsEnumerableProperty) { Debug.Assert(state.Current.JsonPropertyInfo != null); Debug.Assert(state.Current.ReturnValue != null); if (state.Current.TempEnumerableValues != null) { state.Current.TempEnumerableValues.Add(value); } else { IList list = (IList)state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue); if (list == null) { state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value); } else { list.Add(value); } } } else if (state.Current.IsDictionary || (state.Current.IsDictionaryProperty && !setPropertyDirectly)) { Debug.Assert(state.Current.ReturnValue != null); IDictionary dictionary = (IDictionary)state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue); string key = state.Current.KeyName; Debug.Assert(!string.IsNullOrEmpty(key)); dictionary[key] = value; } else if (state.Current.IsIDictionaryConstructible || (state.Current.IsIDictionaryConstructibleProperty && !setPropertyDirectly)) { Debug.Assert(state.Current.TempDictionaryValues != null); IDictionary dictionary = (IDictionary)state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.TempDictionaryValues); string key = state.Current.KeyName; Debug.Assert(!string.IsNullOrEmpty(key)); dictionary[key] = value; } else { Debug.Assert(state.Current.JsonPropertyInfo != null); state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value); } }
/// <summary> /// Returns true if successful, false is the reader ran out of buffer. /// Sets state.Current.ReturnValue to the $ref target for MetadataRefProperty cases. /// </summary> internal static bool ResolveMetadata( JsonConverter converter, ref Utf8JsonReader reader, ref ReadStack state) { if (state.Current.ObjectState < StackFrameObjectState.ReadAheadNameOrEndObject) { // Read the first metadata property name. if (!ReadAheadMetataDataAndSetState(ref reader, ref state, StackFrameObjectState.ReadNameOrEndObject)) { return(false); } } if (state.Current.ObjectState == StackFrameObjectState.ReadNameOrEndObject) { if (reader.TokenType != JsonTokenType.PropertyName) { // An enumerable needs metadata since it starts with StartObject. if (converter.ClassType == ClassType.Enumerable) { ThrowHelper.ThrowJsonException_MetadataPreservedArrayValuesNotFound(converter.TypeToConvert); } // The reader should have detected other invalid cases. Debug.Assert(reader.TokenType == JsonTokenType.EndObject); // Skip the read of the first property name, since we already read it above. state.Current.PropertyState = StackFramePropertyState.ReadName; return(true); } ReadOnlySpan <byte> propertyName = reader.GetSpan(); MetadataPropertyName metadata = GetMetadataPropertyName(propertyName); if (metadata == MetadataPropertyName.Id) { state.Current.JsonPropertyName = propertyName.ToArray(); if (!converter.CanHaveIdMetadata) { ThrowHelper.ThrowJsonException_MetadataCannotParsePreservedObjectIntoImmutable(converter.TypeToConvert); } state.Current.ObjectState = StackFrameObjectState.ReadAheadIdValue; } else if (metadata == MetadataPropertyName.Ref) { state.Current.JsonPropertyName = propertyName.ToArray(); if (converter.IsValueType) { ThrowHelper.ThrowJsonException_MetadataInvalidReferenceToValueType(converter.TypeToConvert); } state.Current.ObjectState = StackFrameObjectState.ReadAheadRefValue; } else if (metadata == MetadataPropertyName.Values) { state.Current.JsonPropertyName = propertyName.ToArray(); if (converter.ClassType == ClassType.Enumerable) { ThrowHelper.ThrowJsonException_MetadataMissingIdBeforeValues(); } else { ThrowHelper.ThrowJsonException_MetadataInvalidPropertyWithLeadingDollarSign(propertyName, ref state, reader); } } else { Debug.Assert(metadata == MetadataPropertyName.NoMetadata); // Having a StartObject without metadata properties is not allowed. if (converter.ClassType == ClassType.Enumerable) { state.Current.JsonPropertyName = propertyName.ToArray(); ThrowHelper.ThrowJsonException_MetadataPreservedArrayInvalidProperty(converter.TypeToConvert, reader); } // Skip the read of the first property name, since we already read it above. state.Current.PropertyState = StackFramePropertyState.ReadName; return(true); } } if (state.Current.ObjectState == StackFrameObjectState.ReadAheadRefValue) { if (!ReadAheadMetataDataAndSetState(ref reader, ref state, StackFrameObjectState.ReadRefValue)) { return(false); } } else if (state.Current.ObjectState == StackFrameObjectState.ReadAheadIdValue) { if (!ReadAheadMetataDataAndSetState(ref reader, ref state, StackFrameObjectState.ReadIdValue)) { return(false); } } if (state.Current.ObjectState == StackFrameObjectState.ReadRefValue) { if (reader.TokenType != JsonTokenType.String) { ThrowHelper.ThrowJsonException_MetadataValueWasNotString(reader.TokenType); } string key = reader.GetString() !; // todo: https://github.com/dotnet/runtime/issues/32354 state.Current.ReturnValue = state.ReferenceResolver.ResolveReferenceOnDeserialize(key); state.Current.ObjectState = StackFrameObjectState.ReadAheadRefEndObject; } else if (state.Current.ObjectState == StackFrameObjectState.ReadIdValue) { if (reader.TokenType != JsonTokenType.String) { ThrowHelper.ThrowJsonException_MetadataValueWasNotString(reader.TokenType); } state.Current.MetadataId = reader.GetString(); // Clear the MetadataPropertyName since we are done processing Id. state.Current.JsonPropertyName = default; if (converter.ClassType == ClassType.Enumerable) { // Need to Read $values property name. state.Current.ObjectState = StackFrameObjectState.ReadAheadValuesName; } else { // We are done reading metadata. state.Current.ObjectState = StackFrameObjectState.PropertyValue; return(true); } } if (state.Current.ObjectState == StackFrameObjectState.ReadAheadRefEndObject) { if (!ReadAheadMetataDataAndSetState(ref reader, ref state, StackFrameObjectState.ReadRefEndObject)) { return(false); } } if (state.Current.ObjectState == StackFrameObjectState.ReadRefEndObject) { if (reader.TokenType != JsonTokenType.EndObject) { // We just read a property. The only valid next tokens are EndObject and PropertyName. Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); ThrowHelper.ThrowJsonException_MetadataReferenceObjectCannotContainOtherProperties(reader.GetSpan(), ref state); } return(true); } if (state.Current.ObjectState == StackFrameObjectState.ReadAheadValuesName) { if (!ReadAheadMetataDataAndSetState(ref reader, ref state, StackFrameObjectState.ReadValuesName)) { return(false); } } if (state.Current.ObjectState == StackFrameObjectState.ReadValuesName) { if (reader.TokenType != JsonTokenType.PropertyName) { ThrowHelper.ThrowJsonException_MetadataPreservedArrayValuesNotFound(converter.TypeToConvert); } ReadOnlySpan <byte> propertyName = reader.GetSpan(); // Remember the property in case we get an exception. state.Current.JsonPropertyName = propertyName.ToArray(); if (GetMetadataPropertyName(propertyName) != MetadataPropertyName.Values) { ThrowHelper.ThrowJsonException_MetadataPreservedArrayInvalidProperty(converter.TypeToConvert, reader); } state.Current.ObjectState = StackFrameObjectState.ReadAheadValuesStartArray; } if (state.Current.ObjectState == StackFrameObjectState.ReadAheadValuesStartArray) { if (!ReadAheadMetataDataAndSetState(ref reader, ref state, StackFrameObjectState.ReadValuesStartArray)) { return(false); } } if (state.Current.ObjectState == StackFrameObjectState.ReadValuesStartArray) { if (reader.TokenType != JsonTokenType.StartArray) { ThrowHelper.ThrowJsonException_MetadataValuesInvalidToken(reader.TokenType); } state.Current.ObjectState = StackFrameObjectState.PropertyValue; } return(true); }
internal JsonConverter GetConverterInternal(Type typeToConvert) { Debug.Assert(typeToConvert != null); if (_converters.TryGetValue(typeToConvert, out JsonConverter? converter)) { Debug.Assert(converter != null); return(converter); } // Priority 1: If there is a JsonSerializerContext, fetch the converter from there. converter = _context?.GetTypeInfo(typeToConvert)?.PropertyInfoForTypeInfo?.ConverterBase; // Priority 2: Attempt to get custom converter added at runtime. // Currently there is not a way at runtime to override the [JsonConverter] when applied to a property. foreach (JsonConverter item in Converters) { if (item.CanConvert(typeToConvert)) { converter = item; break; } } // Priority 3: Attempt to get converter from [JsonConverter] on the type being converted. if (converter == null) { JsonConverterAttribute?converterAttribute = (JsonConverterAttribute?) GetAttributeThatCanHaveMultiple(typeToConvert, typeof(JsonConverterAttribute)); if (converterAttribute != null) { converter = GetConverterFromAttribute(converterAttribute, typeToConvert: typeToConvert, classTypeAttributeIsOn: typeToConvert, memberInfo: null); } } // Priority 4: Attempt to get built-in converter. if (converter == null) { if (s_defaultSimpleConverters == null || s_defaultFactoryConverters == null) { // (De)serialization using serializer's options-based methods has not yet occurred, so the built-in converters are not rooted. // Even though source-gen code paths do not call this method <i.e. JsonSerializerOptions.GetConverter(Type)>, we do not root all the // built-in converters here since we fetch converters for any type included for source generation from the binded context (Priority 1). Debug.Assert(s_defaultSimpleConverters == null); Debug.Assert(s_defaultFactoryConverters == null); ThrowHelper.ThrowNotSupportedException_BuiltInConvertersNotRooted(typeToConvert); return(null !); } if (s_defaultSimpleConverters.TryGetValue(typeToConvert, out JsonConverter? foundConverter)) { Debug.Assert(foundConverter != null); converter = foundConverter; } else { foreach (JsonConverter item in s_defaultFactoryConverters) { if (item.CanConvert(typeToConvert)) { converter = item; break; } } // Since the object and IEnumerable converters cover all types, we should have a converter. Debug.Assert(converter != null); } } // Allow redirection for generic types or the enum converter. if (converter is JsonConverterFactory factory) { converter = factory.GetConverterInternal(typeToConvert, this); // A factory cannot return null; GetConverterInternal checked for that. Debug.Assert(converter != null); } Type converterTypeToConvert = converter.TypeToConvert; if (!converterTypeToConvert.IsAssignableFromInternal(typeToConvert) && !typeToConvert.IsAssignableFromInternal(converterTypeToConvert)) { ThrowHelper.ThrowInvalidOperationException_SerializationConverterNotCompatible(converter.GetType(), typeToConvert); } // Only cache the value once (de)serialization has occurred since new converters can be added that may change the result. if (_haveTypesBeenCreated) { // A null converter is allowed here and cached. // Ignore failure case here in multi-threaded cases since the cached item will be equivalent. _converters.TryAdd(typeToConvert, converter); } return(converter); }
/// <summary> /// Returns the converter for the specified type. /// </summary> /// <param name="typeToConvert">The type to return a converter for.</param> /// <returns> /// The converter for the given type. /// </returns> /// <exception cref="InvalidOperationException"> /// The configured <see cref="JsonConverter"/> for <paramref name="typeToConvert"/> returned an invalid converter. /// </exception> /// <exception cref="NotSupportedException"> /// There is no compatible <see cref="System.Text.Json.Serialization.JsonConverter"/> /// for <paramref name="typeToConvert"/> or its serializable members. /// </exception> public JsonConverter GetConverter(Type typeToConvert) { if (_converters.TryGetValue(typeToConvert, out JsonConverter? converter)) { Debug.Assert(converter != null); return(converter); } // Priority 2: Attempt to get custom converter added at runtime. // Currently there is not a way at runtime to overide the [JsonConverter] when applied to a property. foreach (JsonConverter item in Converters) { if (item.CanConvert(typeToConvert)) { converter = item; break; } } // Priority 3: Attempt to get converter from [JsonConverter] on the type being converted. if (converter == null) { JsonConverterAttribute?converterAttribute = (JsonConverterAttribute?) GetAttributeThatCanHaveMultiple(typeToConvert, typeof(JsonConverterAttribute)); if (converterAttribute != null) { converter = GetConverterFromAttribute(converterAttribute, typeToConvert: typeToConvert, classTypeAttributeIsOn: typeToConvert, memberInfo: null); } } // Priority 4: Attempt to get built-in converter. if (converter == null) { if (s_defaultSimpleConverters.TryGetValue(typeToConvert, out JsonConverter? foundConverter)) { Debug.Assert(foundConverter != null); converter = foundConverter; } else { foreach (JsonConverter item in s_defaultFactoryConverters) { if (item.CanConvert(typeToConvert)) { converter = item; break; } } // Since the object and IEnumerable converters cover all types, we should have a converter. Debug.Assert(converter != null); } } // Allow redirection for generic types or the enum converter. if (converter is JsonConverterFactory factory) { converter = factory.GetConverterInternal(typeToConvert, this); // A factory cannot return null; GetConverterInternal checked for that. Debug.Assert(converter != null); } Type converterTypeToConvert = converter.TypeToConvert; if (!converterTypeToConvert.IsAssignableFromInternal(typeToConvert) && !typeToConvert.IsAssignableFromInternal(converterTypeToConvert)) { ThrowHelper.ThrowInvalidOperationException_SerializationConverterNotCompatible(converter.GetType(), typeToConvert); } // Only cache the value once (de)serialization has occurred since new converters can be added that may change the result. if (_haveTypesBeenCreated) { // A null converter is allowed here and cached. // Ignore failure case here in multi-threaded cases since the cached item will be equivalent. _converters.TryAdd(typeToConvert, converter); } return(converter); }
public JsonClassInfo(Type type, JsonSerializerOptions options) { Type = type; Options = options; JsonConverter converter = GetConverter( Type, parentClassType: null, // A ClassInfo never has a "parent" class. propertyInfo: null, // A ClassInfo never has a "parent" property. out Type runtimeType, Options); ClassType = converter.ClassType; PropertyInfoForClassInfo = CreatePropertyInfoForClassInfo(Type, runtimeType, converter, Options); switch (ClassType) { case ClassType.Object: { CreateObject = options.MemberAccessorStrategy.CreateConstructor(type); Dictionary <string, JsonPropertyInfo> cache = new Dictionary <string, JsonPropertyInfo>( Options.PropertyNameCaseInsensitive ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal); for (Type?currentType = type; currentType != null; currentType = currentType.BaseType) { foreach (PropertyInfo propertyInfo in currentType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly)) { // Ignore indexers if (propertyInfo.GetIndexParameters().Length > 0) { continue; } // For now we only support public getters\setters if (propertyInfo.GetMethod?.IsPublic == true || propertyInfo.SetMethod?.IsPublic == true) { JsonPropertyInfo jsonPropertyInfo = AddProperty(propertyInfo, currentType, options); Debug.Assert(jsonPropertyInfo != null && jsonPropertyInfo.NameAsString != null); // If the JsonPropertyNameAttribute or naming policy results in collisions, throw an exception. if (!JsonHelpers.TryAdd(cache, jsonPropertyInfo.NameAsString, jsonPropertyInfo)) { JsonPropertyInfo other = cache[jsonPropertyInfo.NameAsString]; if (other.ShouldDeserialize == false && other.ShouldSerialize == false) { // Overwrite the one just added since it has [JsonIgnore]. cache[jsonPropertyInfo.NameAsString] = jsonPropertyInfo; } else if (other.PropertyInfo?.Name != jsonPropertyInfo.PropertyInfo?.Name && (jsonPropertyInfo.ShouldDeserialize == true || jsonPropertyInfo.ShouldSerialize == true)) { // Check for name equality is required to determine when a new slot is used for the member. // Therefore, if names are not the same, there is conflict due to the name policy or attributes. ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameConflict(Type, jsonPropertyInfo); } // else ignore jsonPropertyInfo since it has [JsonIgnore] or it's hidden by a new slot. } } else { if (JsonPropertyInfo.GetAttribute <JsonIncludeAttribute>(propertyInfo) != null) { ThrowHelper.ThrowInvalidOperationException_JsonIncludeOnNonPublicInvalid(propertyInfo, currentType); } // Non-public properties should not be included for (de)serialization. } } } JsonPropertyInfo[] cacheArray; if (DetermineExtensionDataProperty(cache)) { // Remove from cache since it is handled independently. cache.Remove(DataExtensionProperty !.NameAsString !); cacheArray = new JsonPropertyInfo[cache.Count + 1]; // Set the last element to the extension property. cacheArray[cache.Count] = DataExtensionProperty; } else { cacheArray = new JsonPropertyInfo[cache.Count]; } // Copy the dictionary cache to the array cache. cache.Values.CopyTo(cacheArray, 0); // Set the array cache field at this point since it is completely initialized. // It can now be safely accessed by other threads. PropertyCacheArray = cacheArray; // Allow constructor parameter logic to remove items from the dictionary since the JSON // property values will be passed to the constructor and do not call a property setter. if (converter.ConstructorIsParameterized) { InitializeConstructorParameters(cache, converter.ConstructorInfo !); } // Set the dictionary cache field at this point since it is completely initialized. // It can now be safely accessed by other threads. PropertyCache = cache; } break; case ClassType.Enumerable: case ClassType.Dictionary: { ElementType = converter.ElementType; CreateObject = options.MemberAccessorStrategy.CreateConstructor(runtimeType); } break; case ClassType.Value: case ClassType.NewValue: { CreateObject = options.MemberAccessorStrategy.CreateConstructor(type); } break; case ClassType.None: { ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(type); } break; default: Debug.Fail($"Unexpected class type: {ClassType}"); throw new InvalidOperationException(); } }
public JsonClassInfo(Type type, JsonSerializerOptions options) { Type = type; Options = options; JsonConverter converter = GetConverter( Type, parentClassType: null, // A ClassInfo never has a "parent" class. propertyInfo: null, // A ClassInfo never has a "parent" property. out Type runtimeType, Options); ClassType = converter.ClassType; PropertyInfoForClassInfo = CreatePropertyInfoForClassInfo(Type, runtimeType, converter, Options); switch (ClassType) { case ClassType.Object: { CreateObject = options.MemberAccessorStrategy.CreateConstructor(type); Dictionary <string, JsonPropertyInfo> cache = new Dictionary <string, JsonPropertyInfo>( Options.PropertyNameCaseInsensitive ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal); Dictionary <string, PropertyInfo>?ignoredProperties = null; // We start from the most derived type. for (Type?currentType = type; currentType != null; currentType = currentType.BaseType) { foreach (PropertyInfo propertyInfo in currentType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly)) { // Ignore indexers and virtual properties that have overrides that were [JsonIgnore]d. if (propertyInfo.GetIndexParameters().Length > 0 || PropertyIsOverridenAndIgnored(propertyInfo, ignoredProperties)) { 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) { JsonPropertyInfo jsonPropertyInfo = AddProperty(propertyInfo, currentType, options); Debug.Assert(jsonPropertyInfo != null && jsonPropertyInfo.NameAsString != null); string propertyName = propertyInfo.Name; // The JsonPropertyNameAttribute or naming policy resulted in a collision. if (!JsonHelpers.TryAdd(cache, jsonPropertyInfo.NameAsString, jsonPropertyInfo)) { JsonPropertyInfo other = cache[jsonPropertyInfo.NameAsString]; if (other.IsIgnored) { // Overwrite previously cached property since it has [JsonIgnore]. cache[jsonPropertyInfo.NameAsString] = 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.PropertyInfo !.Name != propertyName && // Was a property with the same CLR name ignored? That property hid the current property, // thus, if it was ignored, the current property should be ignored too. ignoredProperties?.ContainsKey(propertyName) != true ) { // Throw if we have two public properties with the same JSON property name, // neither overrides or hides the other, and neither have been ignored. ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameConflict(Type, jsonPropertyInfo); } // Ignore the current property. } if (jsonPropertyInfo.IsIgnored) { (ignoredProperties ??= new Dictionary <string, PropertyInfo>())[propertyName] = propertyInfo;
public JsonClassInfo(Type type, JsonSerializerOptions options) { Type = type; Options = options; JsonConverter converter = GetConverter( Type, parentClassType: null, // A ClassInfo never has a "parent" class. memberInfo: null, // A ClassInfo never has a "parent" property. out Type runtimeType, Options); ClassType = converter.ClassType; JsonNumberHandling?typeNumberHandling = GetNumberHandlingForType(Type); PropertyInfoForClassInfo = CreatePropertyInfoForClassInfo(Type, runtimeType, converter, Options); switch (ClassType) { case ClassType.Object: { CreateObject = Options.MemberAccessorStrategy.CreateConstructor(type); Dictionary <string, JsonPropertyInfo> cache = new Dictionary <string, JsonPropertyInfo>( Options.PropertyNameCaseInsensitive ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal); Dictionary <string, MemberInfo>?ignoredMembers = null; // We start from the most derived type. for (Type?currentType = type; currentType != null; currentType = currentType.BaseType) { const BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly; foreach (PropertyInfo propertyInfo in currentType.GetProperties(bindingFlags)) { // Ignore indexers and virtual properties that have overrides that were [JsonIgnore]d. if (propertyInfo.GetIndexParameters().Length > 0 || PropertyIsOverridenAndIgnored(propertyInfo, 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, typeNumberHandling, cache, ref ignoredMembers); } else { if (JsonPropertyInfo.GetAttribute <JsonIncludeAttribute>(propertyInfo) != null) { ThrowHelper.ThrowInvalidOperationException_JsonIncludeOnNonPublicInvalid(propertyInfo, currentType); } // Non-public properties should not be included for (de)serialization. } } foreach (FieldInfo fieldInfo in currentType.GetFields(bindingFlags)) { if (PropertyIsOverridenAndIgnored(fieldInfo, ignoredMembers)) { continue; } bool hasJsonInclude = JsonPropertyInfo.GetAttribute <JsonIncludeAttribute>(fieldInfo) != null; if (fieldInfo.IsPublic) { if (hasJsonInclude || Options.IncludeFields) { CacheMember(currentType, fieldInfo.FieldType, fieldInfo, typeNumberHandling, cache, ref ignoredMembers); } } else { if (hasJsonInclude) { ThrowHelper.ThrowInvalidOperationException_JsonIncludeOnNonPublicInvalid(fieldInfo, currentType); } // Non-public fields should not be included for (de)serialization. } } } JsonPropertyInfo[] cacheArray; if (DetermineExtensionDataProperty(cache)) { // Remove from cache since it is handled independently. cache.Remove(DataExtensionProperty !.NameAsString); cacheArray = new JsonPropertyInfo[cache.Count + 1]; // Set the last element to the extension property. cacheArray[cache.Count] = DataExtensionProperty; } else { cacheArray = new JsonPropertyInfo[cache.Count]; } // Copy the dictionary cache to the array cache. cache.Values.CopyTo(cacheArray, 0); // These are not accessed by other threads until the current JsonClassInfo instance // is finished initializing and added to the cache on JsonSerializerOptions. PropertyCache = cache; PropertyCacheArray = cacheArray; // Allow constructor parameter logic to remove items from the dictionary since the JSON // property values will be passed to the constructor and do not call a property setter. if (converter.ConstructorIsParameterized) { InitializeConstructorParameters(converter.ConstructorInfo !); } } break; case ClassType.Enumerable: case ClassType.Dictionary: { ElementType = converter.ElementType; CreateObject = Options.MemberAccessorStrategy.CreateConstructor(runtimeType); } break; case ClassType.Value: case ClassType.NewValue: { CreateObject = Options.MemberAccessorStrategy.CreateConstructor(type); } break; case ClassType.None: { ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(type); } break; default: Debug.Fail($"Unexpected class type: {ClassType}"); throw new InvalidOperationException(); } }
public T?SetValue(string propertyName, T?value, Action?assignParent = null) { if (IsReadOnly) { ThrowHelper.ThrowNotSupportedException_NodeCollectionIsReadOnly(); } if (propertyName == null) { throw new ArgumentNullException(nameof(propertyName)); } CreateDictionaryIfThresholdMet(); T?existing = null; if (_propertyDictionary != null) { // Fast path if item doesn't exist in dictionary. if (JsonHelpers.TryAdd(_propertyDictionary, propertyName, value)) { assignParent?.Invoke(); _propertyList.Add(new KeyValuePair <string, T?>(propertyName, value)); return(null); } existing = _propertyDictionary[propertyName]; if (ReferenceEquals(existing, value)) { // Ignore if the same value. return(null); } } int i = FindValueIndex(propertyName); if (i >= 0) { if (_propertyDictionary != null) { _propertyDictionary[propertyName] = value; } else { KeyValuePair <string, T?> current = _propertyList[i]; if (ReferenceEquals(current.Value, value)) { // Ignore if the same value. return(null); } existing = current.Value; } assignParent?.Invoke(); _propertyList[i] = new KeyValuePair <string, T?>(propertyName, value); } else { assignParent?.Invoke(); _propertyDictionary?.Add(propertyName, value); _propertyList.Add(new KeyValuePair <string, T?>(propertyName, value)); Debug.Assert(existing == null); } return(existing); }
private static void HandleStartDictionary(JsonSerializerOptions options, ref ReadStack state) { Debug.Assert(!state.Current.IsProcessingEnumerable()); JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo; if (jsonPropertyInfo == null) { jsonPropertyInfo = state.Current.JsonClassInfo.CreateRootObject(options); } Debug.Assert(jsonPropertyInfo != null); // A nested object or dictionary so push new frame. if (state.Current.CollectionPropertyInitialized) { state.Push(); state.Current.JsonClassInfo = jsonPropertyInfo.ElementClassInfo; state.Current.InitializeJsonPropertyInfo(); JsonClassInfo classInfo = state.Current.JsonClassInfo; if (state.Current.IsProcessingIDictionaryConstructible()) { state.Current.TempDictionaryValues = (IDictionary)classInfo.CreateConcreteDictionary(); state.Current.CollectionPropertyInitialized = true; } else if (state.Current.IsProcessingDictionary()) { if (classInfo.CreateObject == null) { throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(classInfo.Type, parentType: null, memberInfo: null); } state.Current.ReturnValue = classInfo.CreateObject(); state.Current.CollectionPropertyInitialized = true; } else if (state.Current.IsProcessingObject(ClassType.Object)) { if (classInfo.CreateObject == null) { ThrowHelper.ThrowNotSupportedException_DeserializeCreateObjectDelegateIsNull(classInfo.Type); } state.Current.ReturnValue = classInfo.CreateObject(); } else { ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(classInfo.Type); } return; } state.Current.CollectionPropertyInitialized = true; if (state.Current.IsProcessingIDictionaryConstructible()) { JsonClassInfo dictionaryClassInfo; if (jsonPropertyInfo.DeclaredPropertyType == jsonPropertyInfo.ImplementedPropertyType) { dictionaryClassInfo = options.GetOrAddClass(jsonPropertyInfo.RuntimePropertyType); } else { dictionaryClassInfo = options.GetOrAddClass(jsonPropertyInfo.DeclaredPropertyType); } state.Current.TempDictionaryValues = (IDictionary)dictionaryClassInfo.CreateConcreteDictionary(); } else { // Create the dictionary. JsonClassInfo dictionaryClassInfo = jsonPropertyInfo.RuntimeClassInfo; IDictionary value = (IDictionary)dictionaryClassInfo.CreateObject(); if (value != null) { if (state.Current.ReturnValue != null) { state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value); } else { // A dictionary is being returned directly, or a nested dictionary. state.Current.SetReturnValue(value); } } } }
public JsonClassInfo(Type type, JsonSerializerOptions options) { Type = type; Options = options; ClassType = GetClassType( type, parentClassType: type, propertyInfo: null, out Type runtimeType, out Type elementType, out Type nullableUnderlyingType, out MethodInfo addMethod, out JsonConverter converter, checkForAddMethod: true, options); // Ignore properties on enumerable. switch (ClassType) { case ClassType.Object: { CreateObject = options.MemberAccessorStrategy.CreateConstructor(type); PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); Dictionary <string, JsonPropertyInfo> cache = CreatePropertyCache(properties.Length); foreach (PropertyInfo propertyInfo in properties) { // Ignore indexers if (propertyInfo.GetIndexParameters().Length > 0) { continue; } // For now we only support public getters\setters if (propertyInfo.GetMethod?.IsPublic == true || propertyInfo.SetMethod?.IsPublic == true) { JsonPropertyInfo jsonPropertyInfo = AddProperty(propertyInfo.PropertyType, propertyInfo, type, options); Debug.Assert(jsonPropertyInfo != null); // If the JsonPropertyNameAttribute or naming policy results in collisions, throw an exception. if (!JsonHelpers.TryAdd(cache, jsonPropertyInfo.NameAsString, jsonPropertyInfo)) { JsonPropertyInfo other = cache[jsonPropertyInfo.NameAsString]; if (other.ShouldDeserialize == false && other.ShouldSerialize == false) { // Overwrite the one just added since it has [JsonIgnore]. cache[jsonPropertyInfo.NameAsString] = jsonPropertyInfo; } else if (jsonPropertyInfo.ShouldDeserialize == true || jsonPropertyInfo.ShouldSerialize == true) { ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameConflict(this, jsonPropertyInfo); } // else ignore jsonPropertyInfo since it has [JsonIgnore]. } } } JsonPropertyInfo[] cacheArray; if (DetermineExtensionDataProperty(cache)) { // Remove from cache since it is handled independently. cache.Remove(DataExtensionProperty.NameAsString); cacheArray = new JsonPropertyInfo[cache.Count + 1]; // Set the last element to the extension property. cacheArray[cache.Count] = DataExtensionProperty; } else { cacheArray = new JsonPropertyInfo[cache.Count]; } // Set fields when finished to avoid concurrency issues. PropertyCache = cache; cache.Values.CopyTo(cacheArray, 0); PropertyCacheArray = cacheArray; } break; case ClassType.Enumerable: case ClassType.Dictionary: { // Add a single property that maps to the class type so we can have policies applied. ElementType = elementType; AddItemToObject = addMethod; // 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, elements from collections and // dictionaries, and collections themselves. Typically it would represent a CLR type such as System.String. PolicyProperty = CreateProperty( declaredPropertyType: type, runtimePropertyType: runtimeType, propertyInfo: null, // Not a real property so this is null. parentClassType: typeof(object), collectionElementType: elementType, nullableUnderlyingType, converter: null, ClassType, options); CreateObject = options.MemberAccessorStrategy.CreateConstructor(PolicyProperty.RuntimePropertyType); } break; case ClassType.Value: { CreateObject = options.MemberAccessorStrategy.CreateConstructor(type); // Add a single property that maps to the class type so we can have policies applied. //AddPolicyPropertyForValue(type, options); PolicyProperty = CreateProperty( declaredPropertyType: type, runtimePropertyType: runtimeType, propertyInfo: null, // Not a real property so this is null. parentClassType: typeof(object), collectionElementType: null, nullableUnderlyingType, converter, ClassType, options); } break; case ClassType.Unknown: { CreateObject = options.MemberAccessorStrategy.CreateConstructor(type); // Add a single property that maps to the class type so we can have policies applied. //AddPolicyPropertyForValue(type, options); PolicyProperty = CreateProperty( declaredPropertyType: type, runtimePropertyType: runtimeType, propertyInfo: null, // Not a real property so this is null. parentClassType: typeof(object), collectionElementType: null, nullableUnderlyingType, converter, ClassType, options); PropertyCache = new Dictionary <string, JsonPropertyInfo>(); PropertyCacheArray = Array.Empty <JsonPropertyInfo>(); } break; default: Debug.Fail($"Unexpected class type: {ClassType}"); break; } }
private static bool TryParseValue(ref Utf8JsonReader reader, [NotNullWhen(true)] out JsonDocument?document, bool shouldThrow) { JsonReaderState state = reader.CurrentState; CheckSupportedOptions(state.Options, nameof(reader)); // Value copy to overwrite the ref on an exception and undo the destructive reads. Utf8JsonReader restore = reader; ReadOnlySpan <byte> valueSpan = default; ReadOnlySequence <byte> valueSequence = default; try { switch (reader.TokenType) { // A new reader was created and has never been read, // so we need to move to the first token. // (or a reader has terminated and we're about to throw) case JsonTokenType.None: // Using a reader loop the caller has identified a property they wish to // hydrate into a JsonDocument. Move to the value first. case JsonTokenType.PropertyName: { if (!reader.Read()) { if (shouldThrow) { ThrowHelper.ThrowJsonReaderException( ref reader, ExceptionResource.ExpectedJsonTokens); } reader = restore; document = null; return(false); } break; } } switch (reader.TokenType) { // Any of the "value start" states are acceptable. case JsonTokenType.StartObject: case JsonTokenType.StartArray: { long startingOffset = reader.TokenStartIndex; if (!reader.TrySkip()) { if (shouldThrow) { ThrowHelper.ThrowJsonReaderException( ref reader, ExceptionResource.ExpectedJsonTokens); } reader = restore; document = null; return(false); } long totalLength = reader.BytesConsumed - startingOffset; ReadOnlySequence <byte> sequence = reader.OriginalSequence; if (sequence.IsEmpty) { valueSpan = reader.OriginalSpan.Slice( checked ((int)startingOffset), checked ((int)totalLength)); } else { valueSequence = sequence.Slice(startingOffset, totalLength); } Debug.Assert( reader.TokenType == JsonTokenType.EndObject || reader.TokenType == JsonTokenType.EndArray); break; } // Single-token values case JsonTokenType.Number: case JsonTokenType.True: case JsonTokenType.False: case JsonTokenType.Null: { if (reader.HasValueSequence) { valueSequence = reader.ValueSequence; } else { valueSpan = reader.ValueSpan; } break; } // String's ValueSequence/ValueSpan omits the quotes, we need them back. case JsonTokenType.String: { ReadOnlySequence <byte> sequence = reader.OriginalSequence; if (sequence.IsEmpty) { // Since the quoted string fit in a ReadOnlySpan originally // the contents length plus the two quotes can't overflow. int payloadLength = reader.ValueSpan.Length + 2; Debug.Assert(payloadLength > 1); ReadOnlySpan <byte> readerSpan = reader.OriginalSpan; Debug.Assert( readerSpan[(int)reader.TokenStartIndex] == (byte)'"', $"Calculated span starts with {readerSpan[(int)reader.TokenStartIndex]}"); Debug.Assert( readerSpan[(int)reader.TokenStartIndex + payloadLength - 1] == (byte)'"', $"Calculated span ends with {readerSpan[(int)reader.TokenStartIndex + payloadLength - 1]}"); valueSpan = readerSpan.Slice((int)reader.TokenStartIndex, payloadLength); } else { long payloadLength = 2; if (reader.HasValueSequence) { payloadLength += reader.ValueSequence.Length; } else { payloadLength += reader.ValueSpan.Length; } valueSequence = sequence.Slice(reader.TokenStartIndex, payloadLength); Debug.Assert( valueSequence.First.Span[0] == (byte)'"', $"Calculated sequence starts with {valueSequence.First.Span[0]}"); Debug.Assert( valueSequence.ToArray()[payloadLength - 1] == (byte)'"', $"Calculated sequence ends with {valueSequence.ToArray()[payloadLength - 1]}"); } break; } default: { if (shouldThrow) { // Default case would only hit if TokenType equals JsonTokenType.EndObject or JsonTokenType.EndArray in which case it would never be sequence Debug.Assert(!reader.HasValueSequence); byte displayByte = reader.ValueSpan[0]; ThrowHelper.ThrowJsonReaderException( ref reader, ExceptionResource.ExpectedStartOfValueNotFound, displayByte); } reader = restore; document = null; return(false); } } } catch { reader = restore; throw; } int length = valueSpan.IsEmpty ? checked ((int)valueSequence.Length) : valueSpan.Length; byte[] rented = ArrayPool <byte> .Shared.Rent(length); Span <byte> rentedSpan = rented.AsSpan(0, length); try { if (valueSpan.IsEmpty) { valueSequence.CopyTo(rentedSpan); } else { valueSpan.CopyTo(rentedSpan); } document = Parse(rented.AsMemory(0, length), state.Options, rented); return(true); } catch { // This really shouldn't happen since the document was already checked // for consistency by Skip. But if data mutations happened just after // the calls to Read then the copy may not be valid. rentedSpan.Clear(); ArrayPool <byte> .Shared.Return(rented); throw; } }
private void DetermineSerializationCapabilities() { if (ClassType != ClassType.Enumerable && ClassType != ClassType.Dictionary && ClassType != ClassType.IDictionaryConstructible) { // We serialize if there is a getter + not ignoring readonly properties. ShouldSerialize = HasGetter && (HasSetter || !Options.IgnoreReadOnlyProperties); // We deserialize if there is a setter. ShouldDeserialize = HasSetter; } else { if (HasGetter) { ShouldSerialize = true; if (HasSetter) { ShouldDeserialize = true; if (RuntimePropertyType.IsArray) { // Verify that we don't have a multidimensional array. if (RuntimePropertyType.GetArrayRank() > 1) { throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(RuntimePropertyType, ParentClassType, PropertyInfo); } EnumerableConverter = s_jsonArrayConverter; } else if (ClassType == ClassType.IDictionaryConstructible) { // Natively supported type. if (DeclaredPropertyType == ImplementedPropertyType) { if (RuntimePropertyType.FullName.StartsWith(JsonClassInfo.ImmutableNamespaceName)) { DefaultImmutableDictionaryConverter.RegisterImmutableDictionary( RuntimePropertyType, JsonClassInfo.GetElementType(RuntimePropertyType, ParentClassType, PropertyInfo, Options), Options); DictionaryConverter = s_jsonImmutableDictionaryConverter; } else if (JsonClassInfo.IsDeserializedByConstructingWithIDictionary(RuntimePropertyType)) { DictionaryConverter = s_jsonIDictionaryConverter; } } // Type that implements a type with ClassType IDictionaryConstructible. else { DictionaryConverter = s_jsonDerivedDictionaryConverter; } } else if (ClassType == ClassType.Enumerable) { // Else if it's an implementing type whose runtime type is not assignable to IList. if (DeclaredPropertyType != ImplementedPropertyType && (!typeof(IList).IsAssignableFrom(RuntimePropertyType) || ImplementedPropertyType == typeof(ArrayList) || ImplementedPropertyType == typeof(IList))) { EnumerableConverter = s_jsonDerivedEnumerableConverter; } else if (JsonClassInfo.IsDeserializedByConstructingWithIList(RuntimePropertyType) || (!typeof(IList).IsAssignableFrom(RuntimePropertyType) && JsonClassInfo.HasConstructorThatTakesGenericIEnumerable(RuntimePropertyType, Options))) { EnumerableConverter = s_jsonICollectionConverter; } else if (RuntimePropertyType.IsGenericType && RuntimePropertyType.FullName.StartsWith(JsonClassInfo.ImmutableNamespaceName) && RuntimePropertyType.GetGenericArguments().Length == 1) { DefaultImmutableEnumerableConverter.RegisterImmutableCollection(RuntimePropertyType, JsonClassInfo.GetElementType(RuntimePropertyType, ParentClassType, PropertyInfo, Options), Options); EnumerableConverter = s_jsonImmutableEnumerableConverter; } } } } } }
private void WriteStart(byte token) { if (CurrentDepth >= JsonConstants.MaxWriterDepth) { ThrowHelper.ThrowInvalidOperationException(ExceptionResource.DepthTooLarge, _currentDepth, token: default, tokenType: default);
private static void HandleStartArray( JsonSerializerOptions options, ref Utf8JsonReader reader, ref ReadStack state) { JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo; if (state.Current.SkipProperty) { // The array is not being applied to the object. state.Push(); state.Current.Drain = true; return; } if (jsonPropertyInfo == null) { jsonPropertyInfo = state.Current.JsonClassInfo.CreateRootObject(options); } else if (state.Current.JsonClassInfo.ClassType == ClassType.Unknown) { jsonPropertyInfo = state.Current.JsonClassInfo.CreatePolymorphicProperty(jsonPropertyInfo, typeof(object), options); } // Verify that we don't have a multidimensional array. Type arrayType = jsonPropertyInfo.RuntimePropertyType; if (!typeof(IEnumerable).IsAssignableFrom(arrayType) || (arrayType.IsArray && arrayType.GetArrayRank() > 1)) { ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(arrayType, reader, state.JsonPath); } Debug.Assert(state.Current.IsProcessingEnumerableOrDictionary); if (state.Current.PropertyInitialized) { // A nested json array so push a new stack frame. Type elementType = jsonPropertyInfo.ElementClassInfo.Type; state.Push(); state.Current.Initialize(elementType, options); state.Current.PropertyInitialized = true; } else { state.Current.PropertyInitialized = true; } jsonPropertyInfo = state.Current.JsonPropertyInfo; if (state.Current.JsonClassInfo.ClassType == ClassType.Value) { state.Current.JsonPropertyInfo.Read(JsonTokenType.StartObject, ref state, ref reader); } else { // If current property is already set (from a constructor, for example) leave as-is. if (jsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue) == null) { // Create the enumerable. object value = ReadStackFrame.CreateEnumerableValue(ref reader, ref state, options); // If value is not null, then we don't have a converter so apply the value. if (value != null) { if (state.Current.ReturnValue != null) { state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value); } else { // Primitive arrays being returned without object state.Current.SetReturnValue(value); } } } } }