// If this method is changed, also change ApplyValueToEnumerable. internal static void ApplyObjectToEnumerable( object value, ref ReadStack state, bool setPropertyDirectly = false) { Debug.Assert(!state.Current.SkipProperty); if (state.Current.IsProcessingObject(ClassType.Enumerable)) { if (state.Current.TempEnumerableValues != null) { state.Current.TempEnumerableValues.Add(value); } else { if (!(state.Current.ReturnValue is IList list)) { ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(value.GetType()); return; } list.Add(value); } } else if (!setPropertyDirectly && state.Current.IsProcessingProperty(ClassType.Enumerable)) { 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 || // ImmutableArray<T> is a struct, so default value won't be null. state.Current.JsonPropertyInfo.RuntimePropertyType.FullName.StartsWith(DefaultImmutableEnumerableConverter.ImmutableArrayGenericTypeName)) { state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value); } else { list.Add(value); } } } else if (state.Current.IsProcessingObject(ClassType.Dictionary) || (state.Current.IsProcessingProperty(ClassType.Dictionary) && !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.IsProcessingObject(ClassType.IDictionaryConstructible) || (state.Current.IsProcessingProperty(ClassType.IDictionaryConstructible) && !setPropertyDirectly)) { Debug.Assert(state.Current.TempDictionaryValues != null); IDictionary dictionary = (IDictionary)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); } }
private static void HandleStartDictionary(JsonSerializerOptions options, ref Utf8JsonReader reader, 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(); state.Current.CollectionPropertyInitialized = true; ClassType classType = state.Current.JsonClassInfo.ClassType; if (classType == ClassType.Value && jsonPropertyInfo.ElementClassInfo.Type != typeof(object) && jsonPropertyInfo.ElementClassInfo.Type != typeof(JsonElement)) { ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(state.Current.JsonClassInfo.Type, reader, state.JsonPath()); } JsonClassInfo classInfo = state.Current.JsonClassInfo; if (state.Current.IsProcessingIDictionaryConstructible) { state.Current.TempDictionaryValues = (IDictionary)classInfo.CreateConcreteDictionary(); } else { if (classInfo.CreateObject == null) { ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(classInfo.Type, reader, state.JsonPath()); return; } state.Current.ReturnValue = classInfo.CreateObject(); } 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); } } } }
private static void ReadCore( JsonSerializerOptions options, ref Utf8JsonReader reader, ref ReadStack readStack) { try { JsonReaderState initialState = 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; } 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)) { // Need more data break; } } else if (readStack.Current.IsProcessingDictionary || readStack.Current.IsProcessingIDictionaryConstructibleOrKeyValuePair) { HandleStartDictionary(options, ref reader, ref readStack); } else { HandleStartObject(options, ref reader, ref readStack); } } else if (tokenType == JsonTokenType.EndObject) { if (readStack.Current.Drain) { readStack.Pop(); } else if (readStack.Current.IsProcessingDictionary || readStack.Current.IsProcessingIDictionaryConstructibleOrKeyValuePair) { HandleEndDictionary(options, ref reader, ref readStack); } else { HandleEndObject(options, ref reader, ref readStack); } } else if (tokenType == JsonTokenType.StartArray) { if (!readStack.Current.IsProcessingValue) { HandleStartArray(options, ref reader, ref readStack); } else if (!HandleObjectAsValue(tokenType, options, ref reader, ref readStack, ref initialState)) { // Need more data break; } } else if (tokenType == JsonTokenType.EndArray) { HandleEndArray(options, ref reader, ref readStack); } else if (tokenType == JsonTokenType.Null) { HandleNull(ref reader, ref readStack, options); } } } catch (JsonReaderException e) { // Re-throw with Path information. ThrowHelper.ReThrowWithPath(e, readStack.JsonPath); } readStack.BytesConsumed += reader.BytesConsumed; return; }
protected abstract void OnReadEnumerable(JsonTokenType tokenType, ref ReadStack state, ref Utf8JsonReader reader);
private static void HandlePropertyName( JsonSerializerOptions options, ref Utf8JsonReader reader, ref ReadStack state) { if (state.Current.Drain) { return; } Debug.Assert(state.Current.ReturnValue != null || state.Current.TempDictionaryValues != null); Debug.Assert(state.Current.JsonClassInfo != null); bool isProcessingDictObject = state.Current.IsProcessingObject(ClassType.Dictionary); if ((isProcessingDictObject || state.Current.IsProcessingProperty(ClassType.Dictionary)) && state.Current.JsonClassInfo.DataExtensionProperty != state.Current.JsonPropertyInfo) { if (isProcessingDictObject) { state.Current.JsonPropertyInfo = state.Current.JsonClassInfo.PolicyProperty; } state.Current.KeyName = reader.GetString(); } else { Debug.Assert(state.Current.JsonClassInfo.ClassType == ClassType.Object); state.Current.EndProperty(); ReadOnlySpan <byte> propertyName = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan; if (reader._stringHasEscaping) { int idx = propertyName.IndexOf(JsonConstants.BackSlash); Debug.Assert(idx != -1); propertyName = GetUnescapedString(propertyName, idx); } JsonPropertyInfo jsonPropertyInfo = state.Current.JsonClassInfo.GetProperty(propertyName, ref state.Current); if (jsonPropertyInfo == JsonPropertyInfo.s_missingProperty) { JsonPropertyInfo?dataExtProperty = state.Current.JsonClassInfo !.DataExtensionProperty; if (dataExtProperty == null) { state.Current.JsonPropertyInfo = JsonPropertyInfo.s_missingProperty; } else { state.Current.JsonPropertyInfo = dataExtProperty; state.Current.JsonPropertyName = propertyName.ToArray(); state.Current.KeyName = JsonHelpers.Utf8GetString(propertyName); state.Current.CollectionPropertyInitialized = true; CreateDataExtensionProperty(dataExtProperty, ref state); } } else { // Support JsonException.Path. Debug.Assert( jsonPropertyInfo.JsonPropertyName == null || options.PropertyNameCaseInsensitive || propertyName.SequenceEqual(jsonPropertyInfo.JsonPropertyName)); state.Current.JsonPropertyInfo = jsonPropertyInfo; if (jsonPropertyInfo.JsonPropertyName == null) { byte[] propertyNameArray = propertyName.ToArray(); if (options.PropertyNameCaseInsensitive) { // Each payload can have a different name here; remember the value on the temporary stack. state.Current.JsonPropertyName = propertyNameArray; } else { // Prevent future allocs by caching globally on the JsonPropertyInfo which is specific to a Type+PropertyName // so it will match the incoming payload except when case insensitivity is enabled (which is handled above). state.Current.JsonPropertyInfo.JsonPropertyName = propertyNameArray; } } } // Increment the PropertyIndex so JsonClassInfo.GetProperty() starts with the next property. state.Current.PropertyIndex++; } }
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.CollectionPropertyInitialized) { // A nested json array so push a new stack frame. Type elementType = jsonPropertyInfo.ElementClassInfo.Type; state.Push(); state.Current.Initialize(elementType, options); } state.Current.CollectionPropertyInitialized = 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); } } } } }
// If this method is changed, also change ApplyObjectToEnumerable. internal static void ApplyValueToEnumerable <TProperty>( ref TProperty value, ref ReadStack state, ref Utf8JsonReader reader) { Debug.Assert(!state.Current.SkipProperty); if (state.Current.IsEnumerable) { if (state.Current.TempEnumerableValues != null) { ((IList <TProperty>)state.Current.TempEnumerableValues).Add(value); } else { ((IList <TProperty>)state.Current.ReturnValue).Add(value); } } else if (state.Current.IsEnumerableProperty) { Debug.Assert(state.Current.JsonPropertyInfo != null); Debug.Assert(state.Current.ReturnValue != null); if (state.Current.TempEnumerableValues != null) { ((IList <TProperty>)state.Current.TempEnumerableValues).Add(value); } else { IList <TProperty> list = (IList <TProperty>)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.IsProcessingDictionary) { Debug.Assert(state.Current.ReturnValue != null); IDictionary <string, TProperty> dictionary = (IDictionary <string, TProperty>)state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue); string key = state.Current.KeyName; Debug.Assert(!string.IsNullOrEmpty(key)); dictionary[key] = value; } else if (state.Current.IsProcessingIDictionaryConstructible) { Debug.Assert(state.Current.TempDictionaryValues != null); IDictionary <string, TProperty> dictionary = (IDictionary <string, TProperty>)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); } }
internal static JsonPropertyInfo LookupProperty( object obj, ref Utf8JsonReader reader, JsonSerializerOptions options, ref ReadStack state, out bool useExtensionProperty, bool createExtensionProperty = true) { Debug.Assert(state.Current.JsonClassInfo.ClassType == ClassType.Object); ReadOnlySpan <byte> unescapedPropertyName = GetPropertyName(ref state, ref reader, options); JsonPropertyInfo jsonPropertyInfo = state.Current.JsonClassInfo.GetProperty(unescapedPropertyName, ref state.Current); // Increment PropertyIndex so GetProperty() starts with the next property the next time this function is called. state.Current.PropertyIndex++; // Determine if we should use the extension property. if (jsonPropertyInfo == JsonPropertyInfo.s_missingProperty) { JsonPropertyInfo?dataExtProperty = state.Current.JsonClassInfo.DataExtensionProperty; if (dataExtProperty != null) { state.Current.JsonPropertyNameAsString = JsonHelpers.Utf8GetString(unescapedPropertyName); if (createExtensionProperty) { CreateDataExtensionProperty(obj, dataExtProperty); } jsonPropertyInfo = dataExtProperty; useExtensionProperty = true; } else { useExtensionProperty = false; } state.Current.JsonPropertyInfo = jsonPropertyInfo; return(jsonPropertyInfo); } // Support JsonException.Path. Debug.Assert( jsonPropertyInfo.JsonPropertyName == null || options.PropertyNameCaseInsensitive || unescapedPropertyName.SequenceEqual(jsonPropertyInfo.JsonPropertyName)); state.Current.JsonPropertyInfo = jsonPropertyInfo; if (jsonPropertyInfo.JsonPropertyName == null) { byte[] propertyNameArray = unescapedPropertyName.ToArray(); if (options.PropertyNameCaseInsensitive) { // Each payload can have a different name here; remember the value on the temporary stack. state.Current.JsonPropertyName = propertyNameArray; } else { // Prevent future allocs by caching globally on the JsonPropertyInfo which is specific to a Type+PropertyName // so it will match the incoming payload except when case insensitivity is enabled (which is handled above). state.Current.JsonPropertyInfo.JsonPropertyName = propertyNameArray; } } state.Current.JsonPropertyInfo = jsonPropertyInfo; useExtensionProperty = false; return(jsonPropertyInfo); }
private static bool HandleNull(ref Utf8JsonReader reader, ref ReadStack state) { if (state.Current.SkipProperty) { return(false); } JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo; if (jsonPropertyInfo == null || (reader.CurrentDepth == 0 && jsonPropertyInfo.CanBeNull)) { Debug.Assert(state.IsLastFrame); Debug.Assert(state.Current.ReturnValue == null); return(true); } Debug.Assert(jsonPropertyInfo != null); if (state.Current.IsCollectionForClass) { AddNullToCollection(jsonPropertyInfo, ref reader, ref state); return(false); } if (state.Current.IsCollectionForProperty) { if (state.Current.CollectionPropertyInitialized) { // Add the element. AddNullToCollection(jsonPropertyInfo, ref reader, ref state); } else { // Set the property to null. ApplyObjectToEnumerable(null, ref state, ref reader, setPropertyDirectly: true); // Reset so that `Is*Property` no longer returns true state.Current.EndProperty(); } return(false); } if (!jsonPropertyInfo.CanBeNull) { // Allow a value type converter to return a null value representation, such as JsonElement. // Most likely this will throw JsonException. jsonPropertyInfo.Read(JsonTokenType.Null, ref state, ref reader); return(false); } if (state.Current.ReturnValue == null) { Debug.Assert(state.IsLastFrame); return(true); } if (!jsonPropertyInfo.IgnoreNullValues) { state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value: null); } return(false); }
private static async ValueTask <TValue> ReadAsync <TValue>( Stream utf8Json, Type returnType, JsonSerializerOptions options = null, CancellationToken cancellationToken = default) { if (options == null) { options = JsonSerializerOptions.s_defaultOptions; } ReadStack readStack = default; readStack.Current.Initialize(returnType, options); var readerState = new JsonReaderState(options.GetReaderOptions()); // todo: switch to ArrayBuffer implementation to handle and simplify the allocs? int utf8BomLength = JsonConstants.Utf8Bom.Length; byte[] buffer = ArrayPool <byte> .Shared.Rent(Math.Max(options.DefaultBufferSize, utf8BomLength)); int bytesInBuffer = 0; long totalBytesRead = 0; int clearMax = 0; bool firstIteration = true; try { while (true) { // Read from the stream until either our buffer is filled or we hit EOF. // Calling ReadCore is relatively expensive, so we minimize the number of times // we need to call it. bool isFinalBlock = false; while (true) { int bytesRead = await utf8Json.ReadAsync( #if BUILDING_INBOX_LIBRARY buffer.AsMemory(bytesInBuffer), #else buffer, bytesInBuffer, buffer.Length - bytesInBuffer, #endif cancellationToken).ConfigureAwait(false); if (bytesRead == 0) { isFinalBlock = true; break; } totalBytesRead += bytesRead; bytesInBuffer += bytesRead; if (bytesInBuffer == buffer.Length) { break; } } if (bytesInBuffer > clearMax) { clearMax = bytesInBuffer; } int start = 0; if (firstIteration) { firstIteration = false; // Handle the UTF-8 BOM if present Debug.Assert(buffer.Length >= JsonConstants.Utf8Bom.Length); if (buffer.AsSpan().StartsWith(JsonConstants.Utf8Bom)) { start += utf8BomLength; bytesInBuffer -= utf8BomLength; } } // Process the data available ReadCore( ref readerState, isFinalBlock, new ReadOnlySpan <byte>(buffer, start, bytesInBuffer), options, ref readStack); Debug.Assert(readStack.BytesConsumed <= bytesInBuffer); int bytesConsumed = checked ((int)readStack.BytesConsumed); bytesInBuffer -= bytesConsumed; if (isFinalBlock) { break; } // Check if we need to shift or expand the buffer because there wasn't enough data to complete deserialization. if ((uint)bytesInBuffer > ((uint)buffer.Length / 2)) { // We have less than half the buffer available, double the buffer size. byte[] dest = ArrayPool <byte> .Shared.Rent((buffer.Length < (int.MaxValue / 2))?buffer.Length * 2 : int.MaxValue); // Copy the unprocessed data to the new buffer while shifting the processed bytes. Buffer.BlockCopy(buffer, bytesConsumed + start, dest, 0, bytesInBuffer); new Span <byte>(buffer, 0, clearMax).Clear(); ArrayPool <byte> .Shared.Return(buffer); clearMax = bytesInBuffer; buffer = dest; } else if (bytesInBuffer != 0) { // Shift the processed bytes to the beginning of buffer to make more room. Buffer.BlockCopy(buffer, bytesConsumed, buffer, 0, bytesInBuffer); } } } finally { // Clear only what we used and return the buffer to the pool new Span <byte>(buffer, 0, clearMax).Clear(); ArrayPool <byte> .Shared.Return(buffer); } if (bytesInBuffer != 0) { ThrowHelper.ThrowJsonException_DeserializeDataRemaining(totalBytesRead, bytesInBuffer); } return((TValue)readStack.Current.ReturnValue); }
private void VerifyRead(JsonTokenType tokenType, int depth, long bytesConsumed, ref ReadStack state, ref Utf8JsonReader reader) { switch (tokenType) { case JsonTokenType.StartArray: if (reader.TokenType != JsonTokenType.EndArray) { ThrowHelper.ThrowJsonException_SerializationConverterRead(reader, state.JsonPath, ConverterBase.ToString()); } else if (depth != reader.CurrentDepth) { ThrowHelper.ThrowJsonException_SerializationConverterRead(reader, state.JsonPath, ConverterBase.ToString()); } // Should not be possible to have not read anything. Debug.Assert(bytesConsumed < reader.BytesConsumed); break; case JsonTokenType.StartObject: if (reader.TokenType != JsonTokenType.EndObject) { ThrowHelper.ThrowJsonException_SerializationConverterRead(reader, state.JsonPath, ConverterBase.ToString()); } else if (depth != reader.CurrentDepth) { ThrowHelper.ThrowJsonException_SerializationConverterRead(reader, state.JsonPath, ConverterBase.ToString()); } // Should not be possible to have not read anything. Debug.Assert(bytesConsumed < reader.BytesConsumed); break; default: // Reading a single property value. if (reader.BytesConsumed != bytesConsumed) { ThrowHelper.ThrowJsonException_SerializationConverterRead(reader, state.JsonPath, ConverterBase.ToString()); } // Should not be possible to change token type. Debug.Assert(reader.TokenType == tokenType); break; } }
private static void HandleEndDictionary(JsonSerializerOptions options, ref Utf8JsonReader reader, ref ReadStack state) { if (state.Current.IsDictionaryProperty) { // We added the items to the dictionary already. state.Current.EndProperty(); } else if (state.Current.IsIDictionaryConstructibleProperty) { Debug.Assert(state.Current.TempDictionaryValues != null); JsonDictionaryConverter converter = state.Current.JsonPropertyInfo.DictionaryConverter; state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, converter.CreateFromDictionary(ref state, state.Current.TempDictionaryValues, options)); state.Current.EndProperty(); } else { object value; if (state.Current.TempDictionaryValues != null) { JsonDictionaryConverter converter = state.Current.JsonPropertyInfo.DictionaryConverter; value = converter.CreateFromDictionary(ref state, state.Current.TempDictionaryValues, options); } else { value = state.Current.ReturnValue; } if (state.IsLastFrame) { // Set the return value directly since this will be returned to the user. state.Current.Reset(); state.Current.ReturnValue = value; } else { state.Pop(); ApplyObjectToEnumerable(value, ref state, ref reader); } } }
/// <summary> /// Internal version that allows re-entry with preserving ReadStack so that JsonPath works correctly. /// </summary> internal static TValue Deserialize <TValue>(ref Utf8JsonReader reader, JsonSerializerOptions options, ref ReadStack state, string?propertyName = null) { if (options == null) { throw new ArgumentNullException(nameof(options)); } state.Current.InitializeReEntry(typeof(TValue), options, propertyName); JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo !; JsonConverter <TValue> converter = (JsonConverter <TValue>)jsonPropertyInfo.ConverterBase; bool success = converter.TryRead(ref reader, jsonPropertyInfo.RuntimePropertyType !, options, ref state, out TValue value); Debug.Assert(success); // Clear the current property state since we are done processing it. state.Current.EndProperty(); return(value); }
// If this method is changed, also change ApplyObjectToEnumerable. internal static void ApplyValueToEnumerable <TProperty>( ref TProperty value, ref ReadStack state) { Debug.Assert(!state.Current.SkipProperty); if (state.Current.IsProcessingObject(ClassType.Enumerable)) { if (state.Current.TempEnumerableValues != null) { ((IList <TProperty>)state.Current.TempEnumerableValues).Add(value); } else { ((IList <TProperty>)state.Current.ReturnValue).Add(value); } } else if (state.Current.IsProcessingProperty(ClassType.Enumerable)) { Debug.Assert(state.Current.JsonPropertyInfo != null); Debug.Assert(state.Current.ReturnValue != null); if (state.Current.TempEnumerableValues != null) { ((IList <TProperty>)state.Current.TempEnumerableValues).Add(value); } else { IList <TProperty> list = (IList <TProperty>)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.IsProcessingDictionary()) { Debug.Assert(state.Current.ReturnValue != null); object currentDictionary = state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue); string key = state.Current.KeyName; Debug.Assert(!string.IsNullOrEmpty(key)); if (currentDictionary is IDictionary <string, TProperty> genericDict) { Debug.Assert(!genericDict.IsReadOnly); genericDict[key] = value; } else if (currentDictionary is IDictionary dict) { Debug.Assert(!dict.IsReadOnly); dict[key] = value; } else { throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(currentDictionary.GetType(), parentType: null, memberInfo: null); } } else if (state.Current.IsProcessingIDictionaryConstructible()) { Debug.Assert(state.Current.TempDictionaryValues != null); IDictionary <string, TProperty> dictionary = (IDictionary <string, TProperty>)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); } }
private static void ReadValueCore(JsonSerializerOptions options, ref Utf8JsonReader reader, ref ReadStack readStack) { 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()) { 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(readStack, 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); } var newReader = new Utf8JsonReader(rentedSpan, isFinalBlock: true, state: default); ReadCore(options, ref newReader, ref readStack); if (newReader.BytesConsumed != length) { ThrowHelper.ThrowJsonException_DeserializeDataRemaining(length, length - newReader.BytesConsumed); } } catch (JsonException) { reader = restore; throw; } finally { rentedSpan.Clear(); ArrayPool <byte> .Shared.Return(rented); } }
private static bool HandleNull(JsonSerializerOptions options, ref Utf8JsonReader reader, ref ReadStack state) { if (state.Current.SkipProperty) { // 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. state.Current.EndProperty(); return(false); } JsonPropertyInfo?jsonPropertyInfo = state.Current.JsonPropertyInfo; if (jsonPropertyInfo == null || (reader.CurrentDepth == 0 && jsonPropertyInfo.CanBeNull)) { Debug.Assert(state.IsLastFrame); Debug.Assert(state.Current.ReturnValue == null); return(true); } if (state.Current.IsProcessingCollectionObject()) { AddNullToCollection(jsonPropertyInfo, ref reader, ref state); return(false); } if (state.Current.IsProcessingCollectionProperty()) { if (state.Current.CollectionPropertyInitialized) { // Add the element. AddNullToCollection(jsonPropertyInfo, ref reader, ref state); } else { // Set the property to null. ApplyObjectToEnumerable(null, ref state, setPropertyDirectly: true); // Reset so that `Is*Property` no longer returns true state.Current.EndProperty(); } return(false); } if (!jsonPropertyInfo.CanBeNull) { // Allow a value type converter to return a null value representation, such as JsonElement. // Most likely this will throw JsonException. jsonPropertyInfo.Read(JsonTokenType.Null, ref state, ref reader); return(false); } if (state.Current.ReturnValue == null) { Debug.Assert(state.IsLastFrame); return(true); } if (!jsonPropertyInfo.IgnoreNullValues) { jsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value: null); } return(false); }
private static void ReadCore( JsonSerializerOptions options, ref Utf8JsonReader reader, ref ReadStack state) { try { JsonPropertyInfo jsonPropertyInfo = state.Current.JsonClassInfo !.PolicyProperty !; JsonConverter converter = jsonPropertyInfo.ConverterBase; if (!state.IsContinuation) { if (!JsonConverter.SingleValueReadWithReadAhead(converter.ClassType, ref reader, ref state)) { // Read more data until we have the full element. state.BytesConsumed += reader.BytesConsumed; return; } } else { // For a continuation, read ahead here to avoid having to build and then tear // down the call stack if there is more than one buffer fetch necessary. if (!JsonConverter.SingleValueReadWithReadAhead(ClassType.Value, ref reader, ref state)) { state.BytesConsumed += reader.BytesConsumed; return; } } bool success = converter.TryReadAsObject(ref reader, jsonPropertyInfo.RuntimePropertyType !, options, ref state, out object?value); if (success) { state.Current.ReturnValue = value; // Read any trailing whitespace. // If additional whitespace exists after this read, the subsequent call to reader.Read() will throw. reader.Read(); } state.BytesConsumed += reader.BytesConsumed; } catch (JsonReaderException ex) { // Re-throw with Path information. ThrowHelper.ReThrowWithPath(state, ex); } catch (FormatException ex) when(ex.Source == ThrowHelper.ExceptionSourceValueToRethrowAsJsonException) { ThrowHelper.ReThrowWithPath(state, reader, ex); } catch (InvalidOperationException ex) when(ex.Source == ThrowHelper.ExceptionSourceValueToRethrowAsJsonException) { ThrowHelper.ReThrowWithPath(state, reader, ex); } catch (JsonException ex) { ThrowHelper.AddExceptionInformation(state, reader, ex); throw; } }
private static void AddNullToCollection(JsonPropertyInfo jsonPropertyInfo, ref Utf8JsonReader reader, ref ReadStack state) { JsonPropertyInfo?elementPropertyInfo = jsonPropertyInfo.ElementClassInfo !.PolicyProperty; // if elementPropertyInfo == null then this element doesn't need a converter (an object). if (elementPropertyInfo?.CanBeNull == false) { // Allow a value type converter to return a null value representation. // Most likely this will throw JsonException unless the converter has special logic (like converter for JsonElement). elementPropertyInfo.ReadEnumerable(JsonTokenType.Null, ref state, ref reader); } else { // Assume collection types are reference types and can have null assigned. ApplyObjectToEnumerable(null, ref state); } }
// 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 reference target for $ref cases; /// Sets state.Current.ReturnValue to a new instance for $id cases. /// </summary> internal static bool ResolveMetadataForJsonArray( ref Utf8JsonReader reader, ref ReadStack state, JsonSerializerOptions options) { JsonConverter converter = state.Current.JsonClassInfo.PropertyInfoForClassInfo.ConverterBase; if (state.Current.ObjectState < StackFrameObjectState.ReadAheadNameOrEndObject) { // Read the first metadata property name. if (!TryReadAheadMetadataAndSetState(ref reader, ref state, StackFrameObjectState.ReadNameOrEndObject)) { return(false); } } if (state.Current.ObjectState == StackFrameObjectState.ReadNameOrEndObject) { if (reader.TokenType != JsonTokenType.PropertyName) { // The reader should have detected other invalid cases. Debug.Assert(reader.TokenType == JsonTokenType.EndObject); // An enumerable needs metadata since it starts with StartObject. ThrowHelper.ThrowJsonException_MetadataPreservedArrayValuesNotFound(ref state, converter.TypeToConvert); } ReadOnlySpan <byte> propertyName = reader.GetSpan(); MetadataPropertyName metadata = GetMetadataPropertyName(propertyName); if (metadata == MetadataPropertyName.Id) { state.Current.JsonPropertyName = s_idPropertyName; if (!converter.CanHaveIdMetadata) { ThrowHelper.ThrowJsonException_MetadataCannotParsePreservedObjectIntoImmutable(converter.TypeToConvert); } state.Current.ObjectState = StackFrameObjectState.ReadAheadIdValue; } else if (metadata == MetadataPropertyName.Ref) { state.Current.JsonPropertyName = s_refPropertyName; if (converter.IsValueType) { ThrowHelper.ThrowJsonException_MetadataInvalidReferenceToValueType(converter.TypeToConvert); } state.Current.ObjectState = StackFrameObjectState.ReadAheadRefValue; } else if (metadata == MetadataPropertyName.Values) { ThrowHelper.ThrowJsonException_MetadataMissingIdBeforeValues(ref state, propertyName); } else { Debug.Assert(metadata == MetadataPropertyName.NoMetadata); // Having a StartObject without metadata properties is not allowed. ThrowHelper.ThrowJsonException_MetadataPreservedArrayInvalidProperty(ref state, converter.TypeToConvert, reader); } } if (state.Current.ObjectState == StackFrameObjectState.ReadAheadRefValue) { if (!TryReadAheadMetadataAndSetState(ref reader, ref state, StackFrameObjectState.ReadRefValue)) { return(false); } } else if (state.Current.ObjectState == StackFrameObjectState.ReadAheadIdValue) { if (!TryReadAheadMetadataAndSetState(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.ResolveReference(key); state.Current.ObjectState = StackFrameObjectState.ReadAheadRefEndObject; } else if (state.Current.ObjectState == StackFrameObjectState.ReadIdValue) { if (reader.TokenType != JsonTokenType.String) { ThrowHelper.ThrowJsonException_MetadataValueWasNotString(reader.TokenType); } converter.CreateInstanceForReferenceResolver(ref reader, ref state, options); string referenceId = reader.GetString() !; state.ReferenceResolver.AddReference(referenceId, state.Current.ReturnValue !); // Need to Read $values property name. state.Current.ObjectState = StackFrameObjectState.ReadAheadValuesName; } // Clear the metadata property name that was set in case of failure on ResolverReference/AddReference. state.Current.JsonPropertyName = null; if (state.Current.ObjectState == StackFrameObjectState.ReadAheadRefEndObject) { if (!TryReadAheadMetadataAndSetState(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 (!TryReadAheadMetadataAndSetState(ref reader, ref state, StackFrameObjectState.ReadValuesName)) { return(false); } } if (state.Current.ObjectState == StackFrameObjectState.ReadValuesName) { if (reader.TokenType != JsonTokenType.PropertyName) { ThrowHelper.ThrowJsonException_MetadataPreservedArrayValuesNotFound(ref state, converter.TypeToConvert); } ReadOnlySpan <byte> propertyName = reader.GetSpan(); if (GetMetadataPropertyName(propertyName) != MetadataPropertyName.Values) { ThrowHelper.ThrowJsonException_MetadataPreservedArrayInvalidProperty(ref state, converter.TypeToConvert, reader); } // Remember the property in case we get an exception in one of the array elements. state.Current.JsonPropertyName = s_valuesPropertyName; state.Current.ObjectState = StackFrameObjectState.ReadAheadValuesStartArray; } if (state.Current.ObjectState == StackFrameObjectState.ReadAheadValuesStartArray) { if (!TryReadAheadMetadataAndSetState(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.ValidateEndTokenOnArray = true; state.Current.ObjectState = StackFrameObjectState.CreatedObject; } return(true); }
private static bool HandleEndArray( JsonSerializerOptions options, ref Utf8JsonReader reader, ref ReadStack state) { bool lastFrame = state.IsLastFrame; if (state.Current.Drain) { // The array is not being applied to the object. state.Pop(); return(lastFrame); } IEnumerable value = ReadStackFrame.GetEnumerableValue(state.Current); if (state.Current.TempEnumerableValues != null) { // We have a converter; possibilities: // - Add value to current frame's current property or TempEnumerableValues. // - Add value to previous frame's current property or TempEnumerableValues. // - Set current property on current frame to value. // - Set current property on previous frame to value. // - Set ReturnValue if root frame and value is the actual return value. JsonEnumerableConverter converter = state.Current.JsonPropertyInfo.EnumerableConverter; Debug.Assert(converter != null); value = converter.CreateFromList(ref state, (IList)value, options); state.Current.TempEnumerableValues = null; } else if (state.Current.IsEnumerableProperty) { // We added the items to the list already. state.Current.EndProperty(); return(false); } if (lastFrame) { if (state.Current.ReturnValue == null) { // Returning a converted list or object. state.Current.Reset(); state.Current.ReturnValue = value; return(true); } else if (state.Current.IsEnumerable || state.Current.IsDictionary || state.Current.IsIDictionaryConstructible) { // Returning a non-converted list. return(true); } // else there must be an outer object, so we'll return false here. } else if (state.Current.IsEnumerable) { state.Pop(); } ApplyObjectToEnumerable(value, ref state, ref reader); return(false); }
/// <summary> /// Returns true if successful, false is the reader ran out of buffer. /// Sets state.Current.ReturnValue to the reference target for $ref cases; /// Sets state.Current.ReturnValue to a new instance for $id cases. /// </summary> internal static bool ResolveMetadataForJsonObject( ref Utf8JsonReader reader, ref ReadStack state, JsonSerializerOptions options) { JsonConverter converter = state.Current.JsonClassInfo.PropertyInfoForClassInfo.ConverterBase; if (state.Current.ObjectState < StackFrameObjectState.ReadAheadNameOrEndObject) { // Read the first metadata property name. if (!TryReadAheadMetadataAndSetState(ref reader, ref state, StackFrameObjectState.ReadNameOrEndObject)) { return(false); } } if (state.Current.ObjectState == StackFrameObjectState.ReadNameOrEndObject) { if (reader.TokenType != JsonTokenType.PropertyName) { // Since this was an empty object, we are done reading metadata. state.Current.ObjectState = StackFrameObjectState.PropertyValue; // 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 = s_idPropertyName; if (!converter.CanHaveIdMetadata) { ThrowHelper.ThrowJsonException_MetadataCannotParsePreservedObjectIntoImmutable(converter.TypeToConvert); } state.Current.ObjectState = StackFrameObjectState.ReadAheadIdValue; } else if (metadata == MetadataPropertyName.Ref) { state.Current.JsonPropertyName = s_refPropertyName; if (converter.IsValueType) { ThrowHelper.ThrowJsonException_MetadataInvalidReferenceToValueType(converter.TypeToConvert); } state.Current.ObjectState = StackFrameObjectState.ReadAheadRefValue; } else if (metadata == MetadataPropertyName.Values) { ThrowHelper.ThrowJsonException_MetadataInvalidPropertyWithLeadingDollarSign(propertyName, ref state, reader); } else { Debug.Assert(metadata == MetadataPropertyName.NoMetadata); // We are done reading metadata, the object didn't contain any. state.Current.ObjectState = StackFrameObjectState.PropertyValue; // 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 (!TryReadAheadMetadataAndSetState(ref reader, ref state, StackFrameObjectState.ReadRefValue)) { return(false); } } else if (state.Current.ObjectState == StackFrameObjectState.ReadAheadIdValue) { if (!TryReadAheadMetadataAndSetState(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 referenceId = reader.GetString() !; // todo: https://github.com/dotnet/runtime/issues/32354 state.Current.ReturnValue = state.ReferenceResolver.ResolveReference(referenceId); state.Current.ObjectState = StackFrameObjectState.ReadAheadRefEndObject; } else if (state.Current.ObjectState == StackFrameObjectState.ReadIdValue) { if (reader.TokenType != JsonTokenType.String) { ThrowHelper.ThrowJsonException_MetadataValueWasNotString(reader.TokenType); } converter.CreateInstanceForReferenceResolver(ref reader, ref state, options); string referenceId = reader.GetString() !; state.ReferenceResolver.AddReference(referenceId, state.Current.ReturnValue !); // We are done reading metadata plus we instantiated the object. state.Current.ObjectState = StackFrameObjectState.CreatedObject; } // Clear the metadata property name that was set in case of failure on ResolveReference/AddReference. state.Current.JsonPropertyName = null; if (state.Current.ObjectState == StackFrameObjectState.ReadAheadRefEndObject) { if (!TryReadAheadMetadataAndSetState(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); }
private void VerifyRead(JsonTokenType originalTokenType, int originalDepth, long bytesConsumed, ref ReadStack state, ref Utf8JsonReader reader) { switch (originalTokenType) { case JsonTokenType.StartArray: if (reader.TokenType != JsonTokenType.EndArray) { // todo issue #38550 blocking this: originalDepth != reader.CurrentDepth + 1 ThrowHelper.ThrowJsonException_SerializationConverterRead(reader, state.JsonPath, ConverterBase.ToString()); } break; case JsonTokenType.StartObject: if (reader.TokenType != JsonTokenType.EndObject) { // todo issue #38550 blocking this: originalDepth != reader.CurrentDepth + 1 ThrowHelper.ThrowJsonException_SerializationConverterRead(reader, state.JsonPath, ConverterBase.ToString()); } break; default: // Reading a single property value. if (reader.TokenType != originalTokenType || reader.BytesConsumed != bytesConsumed) { ThrowHelper.ThrowJsonException_SerializationConverterRead(reader, state.JsonPath, ConverterBase.ToString()); } break; } }
private static bool TryReadAheadMetadataAndSetState(ref Utf8JsonReader reader, ref ReadStack state, StackFrameObjectState nextState) { // If we can't read here, the read will be completed at the root API by asking the stream for more data. // Set the state so we know where to resume on re-entry. state.Current.ObjectState = nextState; return(reader.Read()); }
public static void ReThrowWithPath(ref ReadStack state, in Utf8JsonReader reader, Exception ex)
private static void HandleValue(JsonTokenType tokenType, JsonSerializerOptions options, ref Utf8JsonReader reader, ref ReadStack state) { if (state.Current.SkipProperty) { return; } if (state.Current.LastSeenMetadataProperty == MetadataPropertyName.Id || state.Current.LastSeenMetadataProperty == MetadataPropertyName.Ref) { Debug.Assert(options.ReferenceHandling.ShouldReadPreservedReferences()); HandleMetadataPropertyValue(ref reader, ref state); return; } JsonPropertyInfo?jsonPropertyInfo = state.Current.JsonPropertyInfo; Debug.Assert(state.Current.JsonClassInfo != null); if (jsonPropertyInfo == null) { jsonPropertyInfo = state.Current.JsonClassInfo.CreateRootProperty(options); } else if (state.Current.JsonClassInfo.ClassType == ClassType.Unknown) { jsonPropertyInfo = state.Current.JsonClassInfo.GetOrAddPolymorphicProperty(jsonPropertyInfo, typeof(object), options); } jsonPropertyInfo.Read(tokenType, ref state, ref reader); }
private static void HandleEndDictionary(JsonSerializerOptions options, ref Utf8JsonReader reader, ref ReadStack state) { if (state.Current.SkipProperty) { // Todo: determine if this is reachable. return; } if (state.Current.IsDictionaryProperty) { // Handle special case of DataExtensionProperty where we just added a dictionary element to the extension property. // Since the JSON value is not a dictionary element (it's a normal property in JSON) a JsonTokenType.EndObject // encountered here is from the outer object so forward to HandleEndObject(). if (state.Current.JsonClassInfo.DataExtensionProperty == state.Current.JsonPropertyInfo) { HandleEndObject(ref reader, ref state); } else { // We added the items to the dictionary already. state.Current.EndProperty(); } } else if (state.Current.IsIDictionaryConstructibleProperty) { Debug.Assert(state.Current.TempDictionaryValues != null); JsonDictionaryConverter converter = state.Current.JsonPropertyInfo.DictionaryConverter; state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, converter.CreateFromDictionary(ref state, state.Current.TempDictionaryValues, options)); state.Current.EndProperty(); } else { object value; if (state.Current.TempDictionaryValues != null) { JsonDictionaryConverter converter = state.Current.JsonPropertyInfo.DictionaryConverter; value = converter.CreateFromDictionary(ref state, state.Current.TempDictionaryValues, options); } else { value = state.Current.ReturnValue; } if (state.IsLastFrame) { // Set the return value directly since this will be returned to the user. state.Current.Reset(); state.Current.ReturnValue = value; } else { state.Pop(); ApplyObjectToEnumerable(value, ref state, ref reader); } } }
private static TValue ReadCore <TValue>(JsonConverter jsonConverter, ref Utf8JsonReader reader, JsonSerializerOptions options, ref ReadStack state) { if (jsonConverter is JsonConverter <TValue> converter) { // Call the strongly-typed ReadCore that will not box structs. return(converter.ReadCore(ref reader, options, ref state)); } // The non-generic API was called or we have a polymorphic case where TValue is not equal to the T in JsonConverter<T>. object?value = jsonConverter.ReadCoreAsObject(ref reader, options, ref state); Debug.Assert(value == null || value is TValue); return((TValue)value !); }
internal static JsonPropertyInfo LookupProperty( object obj, ref Utf8JsonReader reader, JsonSerializerOptions options, ref ReadStack state, out bool useExtensionProperty) { Debug.Assert(state.Current.JsonClassInfo.ClassType == ClassType.Object); JsonPropertyInfo jsonPropertyInfo; ReadOnlySpan <byte> unescapedPropertyName; ReadOnlySpan <byte> propertyName = reader.GetSpan(); if (reader._stringHasEscaping) { int idx = propertyName.IndexOf(JsonConstants.BackSlash); Debug.Assert(idx != -1); unescapedPropertyName = GetUnescapedString(propertyName, idx); } else { unescapedPropertyName = propertyName; } if (options.ReferenceHandling.ShouldReadPreservedReferences()) { if (propertyName.Length > 0 && propertyName[0] == '$') { ThrowHelper.ThrowUnexpectedMetadataException(propertyName, ref reader, ref state); } } jsonPropertyInfo = state.Current.JsonClassInfo.GetProperty(unescapedPropertyName, ref state.Current); // Increment PropertyIndex so GetProperty() starts with the next property the next time this function is called. state.Current.PropertyIndex++; // Determine if we should use the extension property. if (jsonPropertyInfo == JsonPropertyInfo.s_missingProperty) { JsonPropertyInfo?dataExtProperty = state.Current.JsonClassInfo.DataExtensionProperty; if (dataExtProperty != null) { state.Current.JsonPropertyNameAsString = JsonHelpers.Utf8GetString(unescapedPropertyName); CreateDataExtensionProperty(obj, dataExtProperty); jsonPropertyInfo = dataExtProperty; } state.Current.JsonPropertyInfo = jsonPropertyInfo; useExtensionProperty = true; return(jsonPropertyInfo); } // Support JsonException.Path. Debug.Assert( jsonPropertyInfo.JsonPropertyName == null || options.PropertyNameCaseInsensitive || unescapedPropertyName.SequenceEqual(jsonPropertyInfo.JsonPropertyName)); state.Current.JsonPropertyInfo = jsonPropertyInfo; if (jsonPropertyInfo.JsonPropertyName == null) { byte[] propertyNameArray = unescapedPropertyName.ToArray(); if (options.PropertyNameCaseInsensitive) { // Each payload can have a different name here; remember the value on the temporary stack. state.Current.JsonPropertyName = propertyNameArray; } else { // Prevent future allocs by caching globally on the JsonPropertyInfo which is specific to a Type+PropertyName // so it will match the incoming payload except when case insensitivity is enabled (which is handled above). state.Current.JsonPropertyInfo.JsonPropertyName = propertyNameArray; } } state.Current.JsonPropertyInfo = jsonPropertyInfo; useExtensionProperty = false; return(jsonPropertyInfo); }
private static void HandleStartArray( JsonSerializerOptions options, ref Utf8JsonReader reader, ref ReadStack state) { if (state.Current.SkipProperty) { // The array is not being applied to the object. state.Push(); state.Current.Drain = true; return; } JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo; if (jsonPropertyInfo == null) { jsonPropertyInfo = state.Current.JsonClassInfo.CreateRootObject(options); } // Verify that we are processing a valid enumerable or dictionary. if (((ClassType.Enumerable | ClassType.Dictionary | ClassType.IDictionaryConstructible) & jsonPropertyInfo.ClassType) == 0) { ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(jsonPropertyInfo.RuntimePropertyType); } if (state.Current.CollectionPropertyInitialized) { // An array nested in a dictionary or array, so push a new stack frame. Type elementType = jsonPropertyInfo.ElementClassInfo.Type; state.Push(); state.Current.Initialize(elementType, options); } state.Current.CollectionPropertyInitialized = true; // The current JsonPropertyInfo will be null if the current type is not one of // ClassType.Value | ClassType.Enumerable | ClassType.Dictionary. // We should not see ClassType.Value here because we handle it on a different code // path invoked in the main read loop. // Only ClassType.Enumerable is valid here since we just saw a StartArray token. if (state.Current.JsonPropertyInfo == null || state.Current.JsonPropertyInfo.ClassType != ClassType.Enumerable) { ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(state.Current.JsonClassInfo.Type); } // Set or replace the existing enumerable value. object value = ReadStackFrame.CreateEnumerableValue(ref reader, ref state); // 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); } } }