public static void RunSample() { Console.WriteLine("**Utf8JsonReader Sample***"); var jsonBytes = File.ReadAllBytes("sample.json"); var jsonSpan = jsonBytes.AsSpan(); var json = new System.Text.Json.Utf8JsonReader(jsonSpan); while (json.Read()) { Console.WriteLine(GetTokenDesc(json)); } }
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) { state = newReader.CurrentState; ThrowHelper.ThrowJsonException_DeserializeDataRemaining(length, length - state.BytesConsumed); } } catch (JsonException) { reader = restore; throw; } finally { rentedSpan.Clear(); ArrayPool <byte> .Shared.Return(rented); } }
private static bool TryParseValue(ref Utf8JsonReader reader, 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; // Placeholder until reader.Skip() is written (#33295) { int depth = reader.CurrentDepth; // CurrentDepth rises late and falls fast, // a payload of "[ 1, 2, 3, 4 ]" will report post-Read() // CurrentDepth values of { 0, 1, 1, 1, 1, 0 }, // Since we're logically at 0 ([), Read() once and keep // reading until we've come back down to 0 (]). do { if (!reader.Read()) { if (shouldThrow) { ThrowHelper.ThrowJsonReaderException( ref reader, ExceptionResource.ExpectedJsonTokens); } reader = restore; document = null; return(false); } } while (reader.CurrentDepth > depth); } 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) { byte displayByte; if (reader.HasValueSequence) { displayByte = reader.ValueSequence.First.Span[0]; } else { 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 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.IsProcessingImmutableDictionary) { 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.IsProcessingImmutableDictionary) { 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; }
public static byte[] ReaderLoop(int inpuDataLength, out int length, ref Utf8JsonReader json) { byte[] outputArray = new byte[inpuDataLength]; Span <byte> destination = outputArray; while (json.Read()) { JsonTokenType tokenType = json.TokenType; ReadOnlySpan <byte> valueSpan = json.HasValueSequence ? json.ValueSequence.ToArray() : json.ValueSpan; if (json.HasValueSequence) { Assert.True(json.ValueSpan == default); if ((tokenType != JsonTokenType.String && tokenType != JsonTokenType.PropertyName) || json.GetString().Length != 0) { // Empty strings could still make this true, i.e. "" Assert.False(json.ValueSequence.IsEmpty); } } else { Assert.True(json.ValueSequence.IsEmpty); if ((tokenType != JsonTokenType.String && tokenType != JsonTokenType.PropertyName) || json.GetString().Length != 0) { // Empty strings could still make this true, i.e. "" Assert.False(json.ValueSpan == default); } } switch (tokenType) { case JsonTokenType.PropertyName: valueSpan.CopyTo(destination); destination[valueSpan.Length] = (byte)','; destination[valueSpan.Length + 1] = (byte)' '; destination = destination.Slice(valueSpan.Length + 2); break; case JsonTokenType.Number: case JsonTokenType.String: case JsonTokenType.Comment: valueSpan.CopyTo(destination); destination[valueSpan.Length] = (byte)','; destination[valueSpan.Length + 1] = (byte)' '; destination = destination.Slice(valueSpan.Length + 2); break; case JsonTokenType.True: // Special casing True/False so that the casing matches with Json.NET destination[0] = (byte)'T'; destination[1] = (byte)'r'; destination[2] = (byte)'u'; destination[3] = (byte)'e'; destination[valueSpan.Length] = (byte)','; destination[valueSpan.Length + 1] = (byte)' '; destination = destination.Slice(valueSpan.Length + 2); break; case JsonTokenType.False: destination[0] = (byte)'F'; destination[1] = (byte)'a'; destination[2] = (byte)'l'; destination[3] = (byte)'s'; destination[4] = (byte)'e'; destination[valueSpan.Length] = (byte)','; destination[valueSpan.Length + 1] = (byte)' '; destination = destination.Slice(valueSpan.Length + 2); break; case JsonTokenType.Null: // Special casing Null so that it matches what JSON.NET does break; case JsonTokenType.StartObject: Assert.True(json.ValueSpan.SequenceEqual(new byte[] { (byte)'{' })); Assert.True(json.ValueSequence.IsEmpty); break; case JsonTokenType.EndObject: Assert.True(json.ValueSpan.SequenceEqual(new byte[] { (byte)'}' })); Assert.True(json.ValueSequence.IsEmpty); break; case JsonTokenType.StartArray: Assert.True(json.ValueSpan.SequenceEqual(new byte[] { (byte)'[' })); Assert.True(json.ValueSequence.IsEmpty); break; case JsonTokenType.EndArray: Assert.True(json.ValueSpan.SequenceEqual(new byte[] { (byte)']' })); Assert.True(json.ValueSequence.IsEmpty); break; default: break; } } length = outputArray.Length - destination.Length; return(outputArray); }
public static Dictionary <string, object> ReaderDictionaryLoop(ref Utf8JsonReader json) { Dictionary <string, object> dictionary = new Dictionary <string, object>(); string key = ""; object value = null; while (json.Read()) { JsonTokenType tokenType = json.TokenType; ReadOnlySpan <byte> valueSpan = json.ValueSpan; switch (tokenType) { case JsonTokenType.PropertyName: key = json.GetString(); dictionary.Add(key, null); break; case JsonTokenType.True: case JsonTokenType.False: value = valueSpan[0] == 't'; if (dictionary.TryGetValue(key, out _)) { dictionary[key] = value; } else { dictionary.Add(key, value); } break; case JsonTokenType.Number: json.TryGetDouble(out double valueDouble); if (dictionary.TryGetValue(key, out _)) { dictionary[key] = valueDouble; } else { dictionary.Add(key, valueDouble); } break; case JsonTokenType.String: string valueString = json.GetString(); if (dictionary.TryGetValue(key, out _)) { dictionary[key] = valueString; } else { dictionary.Add(key, valueString); } break; case JsonTokenType.Null: value = null; if (dictionary.TryGetValue(key, out _)) { dictionary[key] = value; } else { dictionary.Add(key, value); } break; case JsonTokenType.StartObject: Assert.True(valueSpan.SequenceEqual(new byte[] { (byte)'{' })); value = ReaderDictionaryLoop(ref json); if (dictionary.TryGetValue(key, out _)) { dictionary[key] = value; } else { dictionary.Add(key, value); } break; case JsonTokenType.StartArray: Assert.True(valueSpan.SequenceEqual(new byte[] { (byte)'[' })); value = ReaderListLoop(ref json); if (dictionary.TryGetValue(key, out _)) { dictionary[key] = value; } else { dictionary.Add(key, value); } break; case JsonTokenType.EndObject: Assert.True(valueSpan.SequenceEqual(new byte[] { (byte)'}' })); return(dictionary); case JsonTokenType.None: case JsonTokenType.Comment: default: break; } } return(dictionary); }
/// <summary> /// Parses a string representing JSON document into <see cref="JsonNode"/>. /// </summary> /// <param name="json">JSON to parse.</param> /// <param name="options">Options to control the parsing behavior.</param> /// <returns><see cref="JsonNode"/> representation of <paramref name="json"/>.</returns> public static JsonNode Parse(string json, JsonNodeOptions options = default) { Utf8JsonReader reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(json), options.GetReaderOptions()); var currentNodes = new Stack <KeyValuePair <string?, JsonNode?> >(); // nodes currently being created JsonNode?toReturn = null; while (reader.Read()) { JsonTokenType tokenType = reader.TokenType; currentNodes.TryPeek(out KeyValuePair <string?, JsonNode?> currentPair); void AddNewPair(JsonNode jsonNode, bool keepInCurrentNodes = false) { KeyValuePair <string?, JsonNode?> newProperty; if (currentPair.Value == null) { // If previous token was property name, // it was added to stack with not null name and null value, // otherwise, this is first JsonNode added if (currentPair.Key != null) { // Create as property, keep name, replace null with new JsonNode: currentNodes.Pop(); newProperty = new KeyValuePair <string?, JsonNode?>(currentPair.Key, jsonNode); } else { // Add first JsonNode: newProperty = new KeyValuePair <string?, JsonNode?>(null, jsonNode); } } else { // Create as value: newProperty = new KeyValuePair <string?, JsonNode?>(null, jsonNode); } if (keepInCurrentNodes) { // If after adding property, it should be kept in currentNodes, it must be JsonObject or JsonArray Debug.Assert(jsonNode.ValueKind == JsonValueKind.Object || jsonNode.ValueKind == JsonValueKind.Array); currentNodes.Push(newProperty); } else { AddToParent(newProperty, ref currentNodes, ref toReturn, options.DuplicatePropertyNameHandling); } } switch (tokenType) { case JsonTokenType.StartObject: AddNewPair(new JsonObject(), true); break; case JsonTokenType.EndObject: Debug.Assert(currentPair.Value is JsonObject); currentNodes.Pop(); AddToParent(currentPair, ref currentNodes, ref toReturn, options.DuplicatePropertyNameHandling); break; case JsonTokenType.StartArray: AddNewPair(new JsonArray(), true); break; case JsonTokenType.EndArray: Debug.Assert(currentPair.Value is JsonArray); currentNodes.Pop(); AddToParent(currentPair, ref currentNodes, ref toReturn, options.DuplicatePropertyNameHandling); break; case JsonTokenType.PropertyName: currentNodes.Push(new KeyValuePair <string?, JsonNode?>(reader.GetString(), null)); break; case JsonTokenType.Number: AddNewPair(new JsonNumber(JsonHelpers.Utf8GetString(reader.ValueSpan))); break; case JsonTokenType.String: AddNewPair(new JsonString(reader.GetString() !)); break; case JsonTokenType.True: AddNewPair(new JsonBoolean(true)); break; case JsonTokenType.False: AddNewPair(new JsonBoolean(false)); break; case JsonTokenType.Null: AddNewPair(JsonNull.Instance); break; } } Debug.Assert(toReturn != null); return(toReturn); }
private static Utf8JsonReader GetReaderScopedToNextValue(ref Utf8JsonReader reader, scoped ref ReadStack state) { // Advances the provided reader, validating that it is pointing to a complete JSON value. // If successful, returns a new Utf8JsonReader that is scoped to the next value, reusing existing buffers. 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 is JsonTokenType.EndObject or 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> originalSequence = reader.OriginalSequence; if (originalSequence.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 = reader.HasValueSequence ? reader.ValueSequence.Length + 2 : reader.ValueSpan.Length + 2; valueSequence = originalSequence.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 = reader.HasValueSequence ? reader.ValueSequence.First.Span[0] : reader.ValueSpan[0]; ThrowHelper.ThrowJsonReaderException( ref reader, ExceptionResource.ExpectedStartOfValueNotFound, displayByte); break; } } catch (JsonReaderException ex) { // Re-throw with Path information. ThrowHelper.ReThrowWithPath(ref state, ex); } Debug.Assert(!valueSpan.IsEmpty ^ !valueSequence.IsEmpty); return(valueSpan.IsEmpty ? new Utf8JsonReader(valueSequence, reader.CurrentState.Options) : new Utf8JsonReader(valueSpan, reader.CurrentState.Options)); }
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 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 || readStack.Current.IsProcessingIDictionaryConstructible) { HandleStartDictionary(options, ref reader, 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 || readStack.Current.IsProcessingIDictionaryConstructible) { HandleEndDictionary(options, ref reader, ref readStack); } else { HandleEndObject(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, initialBytesConsumed)) { // 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); } } } 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; return; }
internal static bool TryReadMetadata(JsonConverter converter, JsonTypeInfo jsonTypeInfo, ref Utf8JsonReader reader, scoped ref ReadStack state) { Debug.Assert(state.Current.ObjectState == StackFrameObjectState.StartToken); Debug.Assert(state.Current.CanContainMetadata); while (true) { if (state.Current.PropertyState == StackFramePropertyState.None) { state.Current.PropertyState = StackFramePropertyState.ReadName; // Read the property name. if (!reader.Read()) { return(false); } } if (state.Current.PropertyState < StackFramePropertyState.Name) { if (reader.TokenType == JsonTokenType.EndObject) { // Read the entire object while parsing for metadata. return(true); } // We just read a property. The only valid next tokens are EndObject and PropertyName. Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); if (state.Current.MetadataPropertyNames.HasFlag(MetadataPropertyName.Ref)) { // No properties whatsoever should follow a $ref property. ThrowHelper.ThrowJsonException_MetadataReferenceObjectCannotContainOtherProperties(reader.GetSpan(), ref state); } ReadOnlySpan <byte> propertyName = reader.GetSpan(); switch (state.Current.LatestMetadataPropertyName = GetMetadataPropertyName(propertyName, jsonTypeInfo.PolymorphicTypeResolver)) { case MetadataPropertyName.Id: state.Current.JsonPropertyName = s_idPropertyName; if (state.ReferenceResolver is null) { // Found an $id property in a type that doesn't support reference preservation ThrowHelper.ThrowJsonException_MetadataUnexpectedProperty(propertyName, ref state); } if ((state.Current.MetadataPropertyNames & (MetadataPropertyName.Id | MetadataPropertyName.Ref)) != 0) { // No $id or $ref properties should precede $id properties. ThrowHelper.ThrowJsonException_MetadataIdIsNotFirstProperty(propertyName, ref state); } if (!converter.CanHaveMetadata) { // Should not be permitted unless the converter is capable of handling metadata. ThrowHelper.ThrowJsonException_MetadataCannotParsePreservedObjectIntoImmutable(converter.TypeToConvert); } break; case MetadataPropertyName.Ref: state.Current.JsonPropertyName = s_refPropertyName; if (state.ReferenceResolver is null) { // Found a $ref property in a type that doesn't support reference preservation ThrowHelper.ThrowJsonException_MetadataUnexpectedProperty(propertyName, ref state); } if (converter.IsValueType) { // Should not be permitted if the converter is a struct. ThrowHelper.ThrowJsonException_MetadataInvalidReferenceToValueType(converter.TypeToConvert); } if (state.Current.MetadataPropertyNames != 0) { // No metadata properties should precede a $ref property. ThrowHelper.ThrowJsonException_MetadataReferenceObjectCannotContainOtherProperties(reader.GetSpan(), ref state); } break; case MetadataPropertyName.Type: state.Current.JsonPropertyName = jsonTypeInfo.PolymorphicTypeResolver?.TypeDiscriminatorPropertyNameUtf8 ?? s_typePropertyName; if (jsonTypeInfo.PolymorphicTypeResolver is null) { // Found a $type property in a type that doesn't support polymorphism ThrowHelper.ThrowJsonException_MetadataUnexpectedProperty(propertyName, ref state); } if (state.PolymorphicTypeDiscriminator != null) { ThrowHelper.ThrowJsonException_MetadataDuplicateTypeProperty(); } break; case MetadataPropertyName.Values: state.Current.JsonPropertyName = s_valuesPropertyName; if (state.Current.MetadataPropertyNames == MetadataPropertyName.None) { // Cannot have a $values property unless there are preceding metadata properties. ThrowHelper.ThrowJsonException_MetadataStandaloneValuesProperty(ref state, propertyName); } break; default: Debug.Assert(state.Current.LatestMetadataPropertyName == MetadataPropertyName.None); // Encountered a non-metadata property, exit the reader. return(true); } state.Current.PropertyState = StackFramePropertyState.Name; } if (state.Current.PropertyState < StackFramePropertyState.ReadValue) { state.Current.PropertyState = StackFramePropertyState.ReadValue; // Read the property value. if (!reader.Read()) { return(false); } } Debug.Assert(state.Current.PropertyState == StackFramePropertyState.ReadValue); switch (state.Current.LatestMetadataPropertyName) { case MetadataPropertyName.Id: if (reader.TokenType != JsonTokenType.String) { ThrowHelper.ThrowJsonException_MetadataValueWasNotString(reader.TokenType); } if (state.ReferenceId != null) { ThrowHelper.ThrowNotSupportedException_ObjectWithParameterizedCtorRefMetadataNotSupported(s_refPropertyName, ref reader, ref state); } state.ReferenceId = reader.GetString(); break; case MetadataPropertyName.Ref: if (reader.TokenType != JsonTokenType.String) { ThrowHelper.ThrowJsonException_MetadataValueWasNotString(reader.TokenType); } if (state.ReferenceId != null) { ThrowHelper.ThrowNotSupportedException_ObjectWithParameterizedCtorRefMetadataNotSupported(s_refPropertyName, ref reader, ref state); } state.ReferenceId = reader.GetString(); break; case MetadataPropertyName.Type: Debug.Assert(state.PolymorphicTypeDiscriminator == null); switch (reader.TokenType) { case JsonTokenType.String: state.PolymorphicTypeDiscriminator = reader.GetString(); break; case JsonTokenType.Number: state.PolymorphicTypeDiscriminator = reader.GetInt32(); break; default: ThrowHelper.ThrowJsonException_MetadataValueWasNotString(reader.TokenType); break; } break; case MetadataPropertyName.Values: if (reader.TokenType != JsonTokenType.StartArray) { ThrowHelper.ThrowJsonException_MetadataValuesInvalidToken(reader.TokenType); } state.Current.PropertyState = StackFramePropertyState.None; state.Current.MetadataPropertyNames |= state.Current.LatestMetadataPropertyName; return(true); // "$values" property contains the nested payload, exit the metadata reader now. default: Debug.Fail("Non-metadata properties should not reach this stage."); break; } state.Current.MetadataPropertyNames |= state.Current.LatestMetadataPropertyName; state.Current.PropertyState = StackFramePropertyState.None; state.Current.JsonPropertyName = null; } }