From 9a37f12ce0ba518c2fe0b12a4847d797bb6140ef Mon Sep 17 00:00:00 2001 From: Samuel Wanjohi Date: Thu, 6 Nov 2025 10:15:00 +0300 Subject: [PATCH] refactoring to use ReadOnlySpan and ReadOnlyMemory --- .../Json/JsonReaderExtensions.cs | 42 ++-- .../Json/ODataAnnotationNames.cs | 12 +- ...DataJsonBatchPayloadItemPropertiesCache.cs | 22 +- .../Json/ODataJsonBatchReader.cs | 12 +- .../Json/ODataJsonDeserializer.cs | 225 ++++++++++-------- .../Json/ODataJsonErrorDeserializer.cs | 10 +- ...ataJsonPayloadKindDetectionDeserializer.cs | 14 +- .../ODataJsonPropertyAndValueDeserializer.cs | 24 +- .../Json/ODataJsonReaderUtils.cs | 12 +- .../Json/ODataJsonUtils.cs | 6 +- .../Metadata/EdmLibraryExtensions.cs | 38 +-- src/Microsoft.OData.Core/ReaderUtils.cs | 15 +- 12 files changed, 238 insertions(+), 194 deletions(-) diff --git a/src/Microsoft.OData.Core/Json/JsonReaderExtensions.cs b/src/Microsoft.OData.Core/Json/JsonReaderExtensions.cs index 8b0afd714d..d5cffb4733 100644 --- a/src/Microsoft.OData.Core/Json/JsonReaderExtensions.cs +++ b/src/Microsoft.OData.Core/Json/JsonReaderExtensions.cs @@ -76,7 +76,7 @@ internal static void ReadEndArray(this IJsonReader jsonReader) /// /// The to read from. /// The property name of the current property node. - internal static string GetPropertyName(this IJsonReader jsonReader) + internal static ReadOnlySpan GetPropertyName(this IJsonReader jsonReader) { Debug.Assert(jsonReader != null, "jsonReader != null"); Debug.Assert(jsonReader.NodeType == JsonNodeType.Property, "jsonReader.NodeType == JsonNodeType.Property"); @@ -84,7 +84,7 @@ internal static string GetPropertyName(this IJsonReader jsonReader) // NOTE: the JSON reader already verifies that property names are strings and not null/empty string propertyName = (string)jsonReader.GetValue(); - return propertyName; + return propertyName.AsSpan(); } /// @@ -92,12 +92,12 @@ internal static string GetPropertyName(this IJsonReader jsonReader) /// /// The to read from. /// The property name of the property node read. - internal static string ReadPropertyName(this IJsonReader jsonReader) + internal static ReadOnlySpan ReadPropertyName(this IJsonReader jsonReader) { Debug.Assert(jsonReader != null, "jsonReader != null"); jsonReader.ValidateNodeType(JsonNodeType.Property); - string propertyName = jsonReader.GetPropertyName(); + ReadOnlySpan propertyName = jsonReader.GetPropertyName(); jsonReader.ReadNext(); return propertyName; } @@ -260,7 +260,7 @@ internal static void SkipValue(this IJsonReader jsonReader, StringBuilder jsonRa break; case JsonNodeType.Property: - jsonWriter.WriteName(jsonReader.GetPropertyName()); + jsonWriter.WriteName(jsonReader.GetPropertyName().ToString()); break; default: @@ -366,7 +366,7 @@ internal static ODataValue ReadODataValue(this IJsonReader jsonReader) while (jsonReader.NodeType != JsonNodeType.EndObject) { ODataProperty property = new ODataProperty(); - property.Name = jsonReader.ReadPropertyName(); + property.Name = jsonReader.ReadPropertyName().ToString(); property.Value = jsonReader.ReadODataValue(); propertyList.Add(property); } @@ -481,7 +481,7 @@ internal static ValueTask ReadEndArrayAsync(this IJsonReader jsonReader) /// The to read from. /// A task that represents the asynchronous read operation. /// The value of the TResult parameter contains the property name of the current property node. - internal static Task GetPropertyNameAsync(this IJsonReader jsonReader) + internal static Task> GetPropertyNameAsync(this IJsonReader jsonReader) { Debug.Assert(jsonReader != null, "jsonReader != null"); Debug.Assert(jsonReader.NodeType == JsonNodeType.Property, "jsonReader.NodeType == JsonNodeType.Property"); @@ -490,16 +490,16 @@ internal static Task GetPropertyNameAsync(this IJsonReader jsonReader) Task getValueTask = jsonReader.GetValueAsync(); if (getValueTask.IsCompletedSuccessfully) { - return Task.FromResult((string)getValueTask.Result); + return Task.FromResult(((string)getValueTask.Result).AsMemory()); } return AwaitGetValueAsync(getValueTask); - static async Task AwaitGetValueAsync(Task pendingGetValueTask) + static async Task> AwaitGetValueAsync(Task pendingGetValueTask) { object value = await pendingGetValueTask.ConfigureAwait(false); - return (string)value; + return ((string)value).AsMemory(); } } @@ -509,7 +509,7 @@ static async Task AwaitGetValueAsync(Task pendingGetValueTask) /// The to read from. /// A task that represents the asynchronous read operation. /// The value of the TResult parameter contains the property name of the property node read. - internal static Task ReadPropertyNameAsync(this IJsonReader jsonReader) + internal static Task> ReadPropertyNameAsync(this IJsonReader jsonReader) { Debug.Assert(jsonReader != null, "jsonReader != null"); @@ -519,13 +519,13 @@ internal static Task ReadPropertyNameAsync(this IJsonReader jsonReader) } catch (ODataException ex) { - return Task.FromException(ex); + return Task.FromException>(ex); } - Task getPropertyNameTask = jsonReader.GetPropertyNameAsync(); + Task> getPropertyNameTask = jsonReader.GetPropertyNameAsync(); if (getPropertyNameTask.IsCompletedSuccessfully) { - string propertyName = getPropertyNameTask.Result; + ReadOnlyMemory propertyName = getPropertyNameTask.Result; Task readNextTask = jsonReader.ReadNextAsync(); if (readNextTask.IsCompletedSuccessfully) { @@ -537,16 +537,16 @@ internal static Task ReadPropertyNameAsync(this IJsonReader jsonReader) return AwaitGetPropertyNameAsync(jsonReader, getPropertyNameTask); - static async Task AwaitReadNextAsync(Task pendingReadNextTask, string propertyName) + static async Task> AwaitReadNextAsync(Task pendingReadNextTask, ReadOnlyMemory propertyName) { await pendingReadNextTask.ConfigureAwait(false); return propertyName; } - static async Task AwaitGetPropertyNameAsync(IJsonReader jsonReaderParam, Task pendingGetPropertyNameTask) + static async Task> AwaitGetPropertyNameAsync(IJsonReader jsonReaderParam, Task> pendingGetPropertyNameTask) { - string propertyName = await pendingGetPropertyNameTask.ConfigureAwait(false); + ReadOnlyMemory propertyName = await pendingGetPropertyNameTask.ConfigureAwait(false); await jsonReaderParam.ReadNextAsync() .ConfigureAwait(false); @@ -844,9 +844,9 @@ await jsonWriter.EndObjectScopeAsync() break; case JsonNodeType.Property: - string propertyName = await jsonReader.GetPropertyNameAsync() + ReadOnlyMemory propertyName = await jsonReader.GetPropertyNameAsync() .ConfigureAwait(false); - await jsonWriter.WriteNameAsync(propertyName) + await jsonWriter.WriteNameAsync(propertyName.ToString()) .ConfigureAwait(false); break; @@ -1115,8 +1115,8 @@ await jsonReader.ReadStartObjectAsync() while (jsonReader.NodeType != JsonNodeType.EndObject) { ODataProperty property = new ODataProperty(); - property.Name = await jsonReader.ReadPropertyNameAsync() - .ConfigureAwait(false); + property.Name = (await jsonReader.ReadPropertyNameAsync() + .ConfigureAwait(false)).ToString(); property.Value = await jsonReader.ReadODataValueAsync() .ConfigureAwait(false); properties.Add(property); diff --git a/src/Microsoft.OData.Core/Json/ODataAnnotationNames.cs b/src/Microsoft.OData.Core/Json/ODataAnnotationNames.cs index bbcefaffd7..bfcdfcd5a3 100644 --- a/src/Microsoft.OData.Core/Json/ODataAnnotationNames.cs +++ b/src/Microsoft.OData.Core/Json/ODataAnnotationNames.cs @@ -112,11 +112,11 @@ internal static class ODataAnnotationNames /// /// The name of the annotation in question. /// Returns true if the starts with "odata.", false otherwise. - internal static bool IsODataAnnotationName(string annotationName) + internal static bool IsODataAnnotationName(ReadOnlySpan annotationName) { - Debug.Assert(!string.IsNullOrEmpty(annotationName), "!string.IsNullOrEmpty(annotationName)"); + Debug.Assert(!annotationName.IsEmpty, "!string.IsNullOrEmpty(annotationName)"); - if (annotationName.StartsWith(ODataJsonConstants.ODataAnnotationNamespacePrefix, StringComparison.Ordinal)) + if (annotationName.StartsWith(ODataJsonConstants.ODataAnnotationNamespacePrefix.AsSpan(), StringComparison.Ordinal)) { return true; } @@ -129,11 +129,11 @@ internal static bool IsODataAnnotationName(string annotationName) /// /// The annotation name in question. /// Returns true if the starts with "odata." and is not one of the reserved odata annotation names; returns false otherwise. - internal static bool IsUnknownODataAnnotationName(string annotationName) + internal static bool IsUnknownODataAnnotationName(ReadOnlySpan annotationName) { - Debug.Assert(!string.IsNullOrEmpty(annotationName), "!string.IsNullOrEmpty(annotationName)"); + Debug.Assert(!annotationName.IsEmpty, "!string.IsNullOrEmpty(annotationName)"); - if (IsODataAnnotationName(annotationName) && !KnownODataAnnotationNames.Contains(annotationName)) + if (IsODataAnnotationName(annotationName) && !KnownODataAnnotationNames.Contains(annotationName.ToString())) { return true; } diff --git a/src/Microsoft.OData.Core/Json/ODataJsonBatchPayloadItemPropertiesCache.cs b/src/Microsoft.OData.Core/Json/ODataJsonBatchPayloadItemPropertiesCache.cs index d1c89ecb56..d3edc91ac0 100644 --- a/src/Microsoft.OData.Core/Json/ODataJsonBatchPayloadItemPropertiesCache.cs +++ b/src/Microsoft.OData.Core/Json/ODataJsonBatchPayloadItemPropertiesCache.cs @@ -148,13 +148,13 @@ await jsonBatchPayloadItemPropertiesCache.ScanJsonPropertiesAsync() /// /// Name of the property. /// Property value. Null if not found. - internal object GetPropertyValue(string propertyName) + internal object GetPropertyValue(ReadOnlySpan propertyName) { if (this.jsonProperties != null) { - string canonicalPropertyName = Normalize(propertyName); + ReadOnlySpan canonicalPropertyName = Normalize(propertyName); object propertyValue; - if (this.jsonProperties.TryGetValue(canonicalPropertyName, out propertyValue)) + if (this.jsonProperties.TryGetValue(canonicalPropertyName.ToString(), out propertyValue)) { return propertyValue; } @@ -186,9 +186,11 @@ private ODataJsonBatchBodyContentReaderStream CreateJsonPayloadBodyContentStream /// /// Name to be normalized. /// The normalized name. - private static string Normalize(string propertyName) + private static ReadOnlySpan Normalize(ReadOnlySpan propertyName) { - return propertyName.ToUpperInvariant(); + Span buffer = new Span(new char[propertyName.Length]); + propertyName.ToUpperInvariant(buffer); + return buffer; } /// @@ -211,7 +213,7 @@ private void ScanJsonProperties() while (this.jsonReader.NodeType != JsonNodeType.EndObject) { // Convert to upper case to support case-insensitive request property names - string propertyName = Normalize(this.jsonReader.ReadPropertyName()); + string propertyName = Normalize(this.jsonReader.ReadPropertyName()).ToString(); // Throw an ODataException, if a duplicate json property was detected if (jsonProperties.ContainsKey(propertyName)) @@ -265,7 +267,7 @@ private void ScanJsonProperties() while (this.jsonReader.NodeType != JsonNodeType.EndObject) { - string headerName = this.jsonReader.ReadPropertyName(); + string headerName = this.jsonReader.ReadPropertyName().ToString(); string headerValue = this.jsonReader.ReadPrimitiveValue()?.ToString(); // Throw an ODataException, if a duplicate header was detected @@ -362,7 +364,7 @@ await this.jsonReader.ReadStartObjectAsync() while (this.jsonReader.NodeType != JsonNodeType.EndObject) { // Convert to upper case to support case-insensitive request property names - string propertyName = Normalize(await this.jsonReader.ReadPropertyNameAsync().ConfigureAwait(false)); + string propertyName = Normalize((await this.jsonReader.ReadPropertyNameAsync().ConfigureAwait(false)).Span).ToString(); // Throw an ODataException, if a duplicate json property was detected if (jsonProperties.ContainsKey(propertyName)) @@ -416,8 +418,8 @@ await this.jsonReader.ReadStartObjectAsync() while (this.jsonReader.NodeType != JsonNodeType.EndObject) { - string headerName = await this.jsonReader.ReadPropertyNameAsync() - .ConfigureAwait(false); + string headerName = (await this.jsonReader.ReadPropertyNameAsync() + .ConfigureAwait(false)).ToString(); string headerValue = (await this.jsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false))?.ToString(); // Throw an ODataException, if a duplicate header was detected diff --git a/src/Microsoft.OData.Core/Json/ODataJsonBatchReader.cs b/src/Microsoft.OData.Core/Json/ODataJsonBatchReader.cs index 96d351152f..ea9947fd4e 100644 --- a/src/Microsoft.OData.Core/Json/ODataJsonBatchReader.cs +++ b/src/Microsoft.OData.Core/Json/ODataJsonBatchReader.cs @@ -500,12 +500,12 @@ private void DetectReaderMode() this.batchStream.JsonReader.ReadNext(); this.batchStream.JsonReader.ReadStartObject(); - string propertyName = this.batchStream.JsonReader.ReadPropertyName(); - if (PropertyNameRequests.Equals(propertyName, StringComparison.OrdinalIgnoreCase)) + ReadOnlySpan propertyName = this.batchStream.JsonReader.ReadPropertyName(); + if (propertyName.Equals(PropertyNameRequests.AsSpan(), StringComparison.OrdinalIgnoreCase)) { this.mode = ReaderMode.Requests; } - else if (PropertyNameResponses.Equals(propertyName, StringComparison.OrdinalIgnoreCase)) + else if (propertyName.Equals(PropertyNameResponses.AsSpan(), StringComparison.OrdinalIgnoreCase)) { this.mode = ReaderMode.Responses; } @@ -623,14 +623,14 @@ await this.batchStream.JsonReader.ReadNextAsync() await this.batchStream.JsonReader.ReadStartObjectAsync() .ConfigureAwait(false); - string propertyName = await this.batchStream.JsonReader.ReadPropertyNameAsync() + ReadOnlyMemory propertyName = await this.batchStream.JsonReader.ReadPropertyNameAsync() .ConfigureAwait(false); - if (PropertyNameRequests.Equals(propertyName, StringComparison.OrdinalIgnoreCase)) + if (propertyName.Span.Equals(PropertyNameRequests.AsSpan(), StringComparison.OrdinalIgnoreCase)) { this.mode = ReaderMode.Requests; } - else if (PropertyNameResponses.Equals(propertyName, StringComparison.OrdinalIgnoreCase)) + else if (propertyName.Span.Equals(PropertyNameResponses.AsSpan(), StringComparison.OrdinalIgnoreCase)) { this.mode = ReaderMode.Responses; } diff --git a/src/Microsoft.OData.Core/Json/ODataJsonDeserializer.cs b/src/Microsoft.OData.Core/Json/ODataJsonDeserializer.cs index 0e7563a7ce..e32f63e190 100644 --- a/src/Microsoft.OData.Core/Json/ODataJsonDeserializer.cs +++ b/src/Microsoft.OData.Core/Json/ODataJsonDeserializer.cs @@ -225,20 +225,37 @@ private Uri MetadataDocumentUri /// The name of the annotated property, or null if the property is not a property annotation. /// The annotation name, or null if the property is not a property annotation. /// true if the is a property annotation, false otherwise. - internal static bool TryParsePropertyAnnotation(string propertyAnnotationName, out string propertyName, out string annotationName) + internal static bool TryParsePropertyAnnotation(ReadOnlySpan propertyAnnotationName, out ReadOnlySpan propertyName, out ReadOnlySpan annotationName) { - Debug.Assert(!string.IsNullOrEmpty(propertyAnnotationName), "!string.IsNullOrEmpty(propertyAnnotationName)"); + Debug.Assert(!propertyAnnotationName.IsEmpty, "!string.IsNullOrEmpty(propertyAnnotationName)"); - int propertyAnnotationSeparatorIndex = propertyAnnotationName.IndexOf(ODataJsonConstants.ODataPropertyAnnotationSeparatorChar, StringComparison.Ordinal); + int propertyAnnotationSeparatorIndex = propertyAnnotationName.IndexOf(ODataJsonConstants.ODataPropertyAnnotationSeparatorChar); if (propertyAnnotationSeparatorIndex <= 0 || propertyAnnotationSeparatorIndex == propertyAnnotationName.Length - 1) { - propertyName = null; - annotationName = null; + propertyName = default; + annotationName = default; return false; } - propertyName = propertyAnnotationName.Substring(0, propertyAnnotationSeparatorIndex); - annotationName = propertyAnnotationName.Substring(propertyAnnotationSeparatorIndex + 1); + propertyName = propertyAnnotationName.Slice(0, propertyAnnotationSeparatorIndex); + annotationName = propertyAnnotationName.Slice(propertyAnnotationSeparatorIndex + 1); + return true; + } + + internal static bool TryParsePropertyAnnotation(ReadOnlyMemory propertyAnnotationName, out ReadOnlyMemory propertyName, out ReadOnlyMemory annotationName) + { + Debug.Assert(!propertyAnnotationName.IsEmpty, "!string.IsNullOrEmpty(propertyAnnotationName)"); + + int propertyAnnotationSeparatorIndex = propertyAnnotationName.Span.IndexOf(ODataJsonConstants.ODataPropertyAnnotationSeparatorChar); + if (propertyAnnotationSeparatorIndex <= 0 || propertyAnnotationSeparatorIndex == propertyAnnotationName.Length - 1) + { + propertyName = default; + annotationName = default; + return false; + } + + propertyName = propertyAnnotationName.Slice(0, propertyAnnotationSeparatorIndex); + annotationName = propertyAnnotationName.Slice(propertyAnnotationSeparatorIndex + 1); return true; } @@ -510,9 +527,8 @@ internal void ProcessProperty( Debug.Assert(handleProperty != null, "handleProperty != null"); this.AssertJsonCondition(JsonNodeType.Property); - string propertyName; PropertyParsingResult propertyParsingResult = this.ParseProperty( - propertyAndAnnotationCollector, readPropertyAnnotationValue, out propertyName); + propertyAndAnnotationCollector, readPropertyAnnotationValue, out ReadOnlySpan propertyName); while (propertyParsingResult == PropertyParsingResult.CustomInstanceAnnotation && this.ShouldSkipCustomInstanceAnnotation(propertyName)) { @@ -525,11 +541,12 @@ internal void ProcessProperty( propertyAndAnnotationCollector, readPropertyAnnotationValue, out propertyName); } - handleProperty(propertyParsingResult, propertyName); + string propertyNameString = propertyName.ToString(); + handleProperty(propertyParsingResult, propertyNameString); if (propertyParsingResult != PropertyParsingResult.EndOfObject && propertyParsingResult != PropertyParsingResult.CustomInstanceAnnotation) { - propertyAndAnnotationCollector.MarkPropertyAsProcessed(propertyName); + propertyAndAnnotationCollector.MarkPropertyAsProcessed(propertyNameString); } } @@ -581,9 +598,9 @@ internal string ReadContextUriAnnotation( } // Must make sure the input odata.context has a '@' prefix - string propertyName = this.JsonReader.GetPropertyName(); - if (!string.Equals(ODataJsonConstants.PrefixedODataContextPropertyName, propertyName, StringComparison.Ordinal) - && !this.CompareSimplifiedODataAnnotation(ODataJsonConstants.SimplifiedODataContextPropertyName, propertyName)) + ReadOnlySpan propertyName = this.JsonReader.GetPropertyName(); + if (!propertyName.SequenceEqual(ODataJsonConstants.PrefixedODataContextPropertyName) + && !this.CompareSimplifiedODataAnnotation(ODataJsonConstants.SimplifiedODataContextPropertyName.AsSpan(), propertyName)) { if (!failOnMissingContextUriAnnotation || payloadKind == ODataPayloadKind.Unsupported) { @@ -596,7 +613,7 @@ internal string ReadContextUriAnnotation( if (propertyAndAnnotationCollector != null) { - propertyAndAnnotationCollector.MarkPropertyAsProcessed(propertyName); + propertyAndAnnotationCollector.MarkPropertyAsProcessed(propertyName.ToString()); } // Read over the property name @@ -717,11 +734,11 @@ internal async Task ProcessPropertyAsync( Debug.Assert(handlePropertyDelegate != null, $"{nameof(handlePropertyDelegate)} != null"); this.AssertJsonCondition(JsonNodeType.Property); - (PropertyParsingResult propertyParsingResult, string propertyName) = await this.ParsePropertyAsync( + (PropertyParsingResult propertyParsingResult, ReadOnlyMemory propertyName) = await this.ParsePropertyAsync( propertyAndAnnotationCollector, readPropertyAnnotationValueDelegate).ConfigureAwait(false); - while (propertyParsingResult == PropertyParsingResult.CustomInstanceAnnotation && this.ShouldSkipCustomInstanceAnnotation(propertyName)) + while (propertyParsingResult == PropertyParsingResult.CustomInstanceAnnotation && this.ShouldSkipCustomInstanceAnnotation(propertyName.Span)) { // Read over the property name await this.JsonReader.ReadAsync() @@ -735,12 +752,14 @@ await this.JsonReader.SkipValueAsync() readPropertyAnnotationValueDelegate).ConfigureAwait(false); } - await handlePropertyDelegate(propertyParsingResult, propertyName) + string propertyNameString = propertyName.Span.ToString(); + + await handlePropertyDelegate(propertyParsingResult, propertyNameString) .ConfigureAwait(false); if (propertyParsingResult != PropertyParsingResult.EndOfObject && propertyParsingResult != PropertyParsingResult.CustomInstanceAnnotation) { - propertyAndAnnotationCollector.MarkPropertyAsProcessed(propertyName); + propertyAndAnnotationCollector.MarkPropertyAsProcessed(propertyNameString); } } @@ -771,10 +790,10 @@ internal async Task ReadContextUriAnnotationAsync( } // Must make sure the input odata.context has a '@' prefix - string propertyName = await this.JsonReader.GetPropertyNameAsync() + ReadOnlyMemory propertyName = await this.JsonReader.GetPropertyNameAsync() .ConfigureAwait(false); - if (!string.Equals(ODataJsonConstants.PrefixedODataContextPropertyName, propertyName, StringComparison.Ordinal) - && !this.CompareSimplifiedODataAnnotation(ODataJsonConstants.SimplifiedODataContextPropertyName, propertyName)) + if (!propertyName.Equals(ODataJsonConstants.PrefixedODataContextPropertyName) + && !this.CompareSimplifiedODataAnnotation(ODataJsonConstants.SimplifiedODataContextPropertyName.AsSpan(), propertyName.Span)) { if (!failOnMissingContextUriAnnotation || payloadKind == ODataPayloadKind.Unsupported) { @@ -787,7 +806,7 @@ internal async Task ReadContextUriAnnotationAsync( if (propertyAndAnnotationCollector != null) { - propertyAndAnnotationCollector.MarkPropertyAsProcessed(propertyName); + propertyAndAnnotationCollector.MarkPropertyAsProcessed(propertyName.ToString()); } // Read over the property name @@ -803,13 +822,13 @@ await this.JsonReader.ReadNextAsync() /// The simplified OData annotation property name. /// The JSON property name read from the payload. /// If the JSON property name equals the simplified OData annotation property name. - protected bool CompareSimplifiedODataAnnotation(string simplifiedPropertyName, string propertyName) + protected bool CompareSimplifiedODataAnnotation(ReadOnlySpan simplifiedPropertyName, ReadOnlySpan propertyName) { - Debug.Assert(simplifiedPropertyName.IndexOf('@', StringComparison.Ordinal) == 0, "simplifiedPropertyName must start with '@'."); - Debug.Assert(simplifiedPropertyName.IndexOf('.', StringComparison.Ordinal) == -1, "simplifiedPropertyName must not be namespace-qualified."); + Debug.Assert(simplifiedPropertyName.IndexOf('@') == 0, "simplifiedPropertyName must start with '@'."); + Debug.Assert(simplifiedPropertyName.IndexOf('.') == -1, "simplifiedPropertyName must not be namespace-qualified."); return this.JsonInputContext.OptionalODataPrefix && - string.Equals(simplifiedPropertyName, propertyName, StringComparison.Ordinal); + simplifiedPropertyName.SequenceEqual(propertyName); } /// @@ -817,12 +836,30 @@ protected bool CompareSimplifiedODataAnnotation(string simplifiedPropertyName, s /// /// The annotation name to be completed. /// The complete OData annotation name. - protected string CompleteSimplifiedODataAnnotation(string annotationName) + protected ReadOnlySpan CompleteSimplifiedODataAnnotation(ReadOnlySpan annotationName) + { + if (this.JsonInputContext.OptionalODataPrefix && + annotationName.IndexOf('.') == -1) + { + return string.Concat(ODataJsonConstants.ODataAnnotationNamespacePrefix.AsSpan(), annotationName).AsSpan(); + } + + return annotationName; + } + + /// + /// Overload for ReadOnlyMemory<char> to support both span and memory. + /// + /// The annotation name to be completed. + /// The complete OData annotation name as ReadOnlyMemory<char>. + protected ReadOnlyMemory CompleteSimplifiedODataAnnotation(ReadOnlyMemory annotationName) { + // Use the span-based logic and allocate a string if needed if (this.JsonInputContext.OptionalODataPrefix && - annotationName.IndexOf('.', StringComparison.Ordinal) == -1) + annotationName.Span.IndexOf('.') == -1) { - annotationName = ODataJsonConstants.ODataAnnotationNamespacePrefix + annotationName; + var completed = string.Concat(ODataJsonConstants.ODataAnnotationNamespacePrefix, annotationName.ToString()); + return completed.AsMemory(); } return annotationName; @@ -833,7 +870,7 @@ protected string CompleteSimplifiedODataAnnotation(string annotationName) /// /// The custom instance annotation name in question. /// Returns true if should be skipped by the reader; false otherwise. - private bool ShouldSkipCustomInstanceAnnotation(string annotationName) + private bool ShouldSkipCustomInstanceAnnotation(ReadOnlySpan annotationName) { // By default we always reading custom instance annotation on error payloads. if (this is ODataJsonErrorDeserializer && this.MessageReaderSettings.ShouldIncludeAnnotation == null) @@ -845,7 +882,7 @@ private bool ShouldSkipCustomInstanceAnnotation(string annotationName) // the default we want for non-error payloads. // If the readerSettings.AnnotationFilter is set, readerSettings.ShouldSkipCustomInstanceAnnotation() will evaluate the annotationName based // of the filter. This should override the default behavior for both error and non-error payloads. - return this.MessageReaderSettings.ShouldSkipAnnotation(annotationName); + return this.MessageReaderSettings.ShouldSkipAnnotation(annotationName.ToString()); } /// @@ -853,9 +890,9 @@ private bool ShouldSkipCustomInstanceAnnotation(string annotationName) /// /// the origin annotation name from reader /// true is the instance annotation, false is not - private static bool IsInstanceAnnotation(string annotationName) + private static bool IsInstanceAnnotation(ReadOnlySpan annotationName) { - if (!string.IsNullOrEmpty(annotationName) && annotationName[0] == ODataJsonConstants.ODataPropertyAnnotationSeparatorChar) + if (!annotationName.IsEmpty && annotationName[0] == ODataJsonConstants.ODataPropertyAnnotationSeparatorChar) { return true; } @@ -875,9 +912,9 @@ private static bool IsInstanceAnnotation(string annotationName) /// The annotation name in question. /// The annotation value that was read. /// Returns true if the annotation name and value is skipped; returns false otherwise. - private bool SkipOverUnknownODataAnnotation(string annotationName, out object annotationValue) + private bool SkipOverUnknownODataAnnotation(ReadOnlySpan annotationName, out object annotationValue) { - Debug.Assert(!string.IsNullOrEmpty(annotationName), "!string.IsNullOrEmpty(annotationName)"); + Debug.Assert(!annotationName.IsEmpty, "!string.IsNullOrEmpty(annotationName)"); this.AssertJsonCondition(JsonNodeType.Property); if (ODataAnnotationNames.IsUnknownODataAnnotationName(annotationName)) @@ -895,7 +932,7 @@ private bool SkipOverUnknownODataAnnotation(string annotationName, out object an /// /// The annotation name. /// The annotation value. - private object ReadODataOrCustomInstanceAnnotationValue(string annotationName) + private object ReadODataOrCustomInstanceAnnotationValue(ReadOnlySpan annotationName) { // Read over the name. this.JsonReader.Read(); @@ -936,22 +973,22 @@ private object ReadODataOrCustomInstanceAnnotationValue(string annotationName) private PropertyParsingResult ParseProperty( PropertyAndAnnotationCollector propertyAndAnnotationCollector, Func readPropertyAnnotationValue, - out string parsedPropertyName) + out ReadOnlySpan parsedPropertyName) { Debug.Assert(propertyAndAnnotationCollector != null, "propertyAndAnnotationCollector != null"); Debug.Assert(readPropertyAnnotationValue != null, "readPropertyAnnotationValue != null"); - string lastPropertyAnnotationNameFound = null; - parsedPropertyName = null; + ReadOnlySpan lastPropertyAnnotationNameFound = default; + parsedPropertyName = default; while (this.JsonReader.NodeType == JsonNodeType.Property) { - string nameFromReader = this.JsonReader.GetPropertyName(); + ReadOnlySpan nameFromReader = this.JsonReader.GetPropertyName(); - string propertyNameFromReader, annotationNameFromReader; + ReadOnlySpan propertyNameFromReader, annotationNameFromReader; bool isPropertyAnnotation = TryParsePropertyAnnotation(nameFromReader, out propertyNameFromReader, out annotationNameFromReader); // reading a nested delta resource set - if (isPropertyAnnotation && string.Equals(this.CompleteSimplifiedODataAnnotation(annotationNameFromReader), ODataAnnotationNames.ODataDelta, StringComparison.Ordinal)) + if (isPropertyAnnotation && this.CompleteSimplifiedODataAnnotation(annotationNameFromReader).SequenceEqual(ODataAnnotationNames.ODataDelta)) { // Read over the property name. this.JsonReader.Read(); @@ -963,16 +1000,16 @@ private PropertyParsingResult ParseProperty( if (!isPropertyAnnotation) { isInstanceAnnotation = IsInstanceAnnotation(nameFromReader); - propertyNameFromReader = isInstanceAnnotation ? this.CompleteSimplifiedODataAnnotation(nameFromReader.Substring(1)) : nameFromReader; + propertyNameFromReader = isInstanceAnnotation ? this.CompleteSimplifiedODataAnnotation(nameFromReader.Slice(1)) : nameFromReader; } // If parsedPropertyName is set and is different from the property name the reader is currently on, // we have parsed a property annotation for a different property than the one at the current position. - if (parsedPropertyName != null && !string.Equals(parsedPropertyName, propertyNameFromReader, StringComparison.Ordinal)) + if (parsedPropertyName != null && !parsedPropertyName.SequenceEqual(propertyNameFromReader)) { if (ODataJsonReaderUtils.IsAnnotationProperty(parsedPropertyName)) { - throw new ODataException(Error.Format(SRResources.ODataJsonDeserializer_AnnotationTargetingInstanceAnnotationWithoutValue, lastPropertyAnnotationNameFound, parsedPropertyName)); + throw new ODataException(Error.Format(SRResources.ODataJsonDeserializer_AnnotationTargetingInstanceAnnotationWithoutValue, lastPropertyAnnotationNameFound.ToString(), parsedPropertyName.ToString())); } return PropertyParsingResult.PropertyWithoutValue; @@ -988,7 +1025,7 @@ private PropertyParsingResult ParseProperty( // so this.ProcessPropertyAnnotation() will test and fail for that case. if (!ODataJsonReaderUtils.IsAnnotationProperty(propertyNameFromReader) && this.SkipOverUnknownODataAnnotation(annotationNameFromReader, out annotationValue)) { - propertyAndAnnotationCollector.AddODataPropertyAnnotation(propertyNameFromReader, annotationNameFromReader, annotationValue); + propertyAndAnnotationCollector.AddODataPropertyAnnotation(propertyNameFromReader.ToString(), annotationNameFromReader.ToString(), annotationValue); continue; } @@ -1006,7 +1043,7 @@ private PropertyParsingResult ParseProperty( // collect 'odata.' annotation: // here we know the original property name contains no '@', but '.' dot Debug.Assert(annotationNameFromReader == null, "annotationNameFromReader == null"); - propertyAndAnnotationCollector.AddODataScopeAnnotation(propertyNameFromReader, annotationValue); + propertyAndAnnotationCollector.AddODataScopeAnnotation(propertyNameFromReader.ToString(), annotationValue); continue; } @@ -1046,7 +1083,7 @@ private PropertyParsingResult ParseProperty( { if (ODataJsonReaderUtils.IsAnnotationProperty(parsedPropertyName)) { - throw new ODataException(Error.Format(SRResources.ODataJsonDeserializer_AnnotationTargetingInstanceAnnotationWithoutValue, lastPropertyAnnotationNameFound, parsedPropertyName)); + throw new ODataException(Error.Format(SRResources.ODataJsonDeserializer_AnnotationTargetingInstanceAnnotationWithoutValue, lastPropertyAnnotationNameFound.ToString(), parsedPropertyName.ToString())); } return PropertyParsingResult.PropertyWithoutValue; @@ -1062,12 +1099,12 @@ private PropertyParsingResult ParseProperty( /// The annotation targeting the . /// The duplicate property names checker. /// Callback to read the property annotation value. - private void ProcessPropertyAnnotation(string annotatedPropertyName, string annotationName, PropertyAndAnnotationCollector propertyAndAnnotationCollector, Func readPropertyAnnotationValue) + private void ProcessPropertyAnnotation(ReadOnlySpan annotatedPropertyName, ReadOnlySpan annotationName, PropertyAndAnnotationCollector propertyAndAnnotationCollector, Func readPropertyAnnotationValue) { // We don't currently support annotation targeting an instance annotation except for the @odata.type property annotation. - if (ODataJsonReaderUtils.IsAnnotationProperty(annotatedPropertyName) && !string.Equals(annotationName, ODataAnnotationNames.ODataType, StringComparison.Ordinal)) + if (ODataJsonReaderUtils.IsAnnotationProperty(annotatedPropertyName) && !annotationName.SequenceEqual(ODataAnnotationNames.ODataType.AsSpan())) { - throw new ODataException(Error.Format(SRResources.ODataJsonDeserializer_OnlyODataTypeAnnotationCanTargetInstanceAnnotation, annotationName, annotatedPropertyName, ODataAnnotationNames.ODataType)); + throw new ODataException(Error.Format(SRResources.ODataJsonDeserializer_OnlyODataTypeAnnotationCanTargetInstanceAnnotation, annotationName.ToString(), annotatedPropertyName.ToString(), ODataAnnotationNames.ODataType)); } ReadODataOrCustomInstanceAnnotationValue(annotatedPropertyName, annotationName, propertyAndAnnotationCollector, readPropertyAnnotationValue); @@ -1080,26 +1117,26 @@ private void ProcessPropertyAnnotation(string annotatedPropertyName, string anno /// The annotation name /// The PropertyAndAnnotationCollector. /// Callback to read the property annotation value. - private void ReadODataOrCustomInstanceAnnotationValue(string annotatedPropertyName, string annotationName, PropertyAndAnnotationCollector propertyAndAnnotationCollector, Func readPropertyAnnotationValue) + private void ReadODataOrCustomInstanceAnnotationValue(ReadOnlySpan annotatedPropertyName, ReadOnlySpan annotationName, PropertyAndAnnotationCollector propertyAndAnnotationCollector, Func readPropertyAnnotationValue) { // Read over the property name. this.JsonReader.Read(); if (ODataJsonReaderUtils.IsODataAnnotationName(annotationName)) { // OData annotations are read - propertyAndAnnotationCollector.AddODataPropertyAnnotation(annotatedPropertyName, annotationName, readPropertyAnnotationValue(annotationName)); + propertyAndAnnotationCollector.AddODataPropertyAnnotation(annotatedPropertyName.ToString(), annotationName.ToString(), readPropertyAnnotationValue(annotationName.ToString())); } else { if (this.ShouldSkipCustomInstanceAnnotation(annotationName) || (this is ODataJsonErrorDeserializer && this.MessageReaderSettings.ShouldIncludeAnnotation == null)) { - propertyAndAnnotationCollector.CheckIfPropertyOpenForAnnotations(annotatedPropertyName, annotationName); + propertyAndAnnotationCollector.CheckIfPropertyOpenForAnnotations(annotatedPropertyName.ToString(), annotationName.ToString()); this.JsonReader.SkipValue(); } else { Debug.Assert(ReadPropertyCustomAnnotationValue != null, "readPropertyCustomAnnotationValue != null"); - propertyAndAnnotationCollector.AddCustomPropertyAnnotation(annotatedPropertyName, annotationName, ReadPropertyCustomAnnotationValue(propertyAndAnnotationCollector, annotationName)); + propertyAndAnnotationCollector.AddCustomPropertyAnnotation(annotatedPropertyName.ToString(), annotationName.ToString(), ReadPropertyCustomAnnotationValue(propertyAndAnnotationCollector, annotationName.ToString())); } } } @@ -1173,15 +1210,15 @@ private string ReadPayloadStartImplementation( /// 1) true if the annotation name and value is skipped; otherwise false. /// 2) The annotation value that was read. /// - private async ValueTask<(bool IsUnknownODataAnnotationName, object AnnotationValue)> SkipOverUnknownODataAnnotationAsync(string annotationName) + private async ValueTask<(bool IsUnknownODataAnnotationName, object AnnotationValue)> SkipOverUnknownODataAnnotationAsync(ReadOnlyMemory annotationName) { - Debug.Assert(!string.IsNullOrEmpty(annotationName), "!string.IsNullOrEmpty(annotationName)"); + Debug.Assert(!annotationName.IsEmpty, "!string.IsNullOrEmpty(annotationName)"); this.AssertJsonCondition(JsonNodeType.Property); object annotationValue = null; bool isUnknownODataAnnotationName = false; - if (ODataAnnotationNames.IsUnknownODataAnnotationName(annotationName)) + if (ODataAnnotationNames.IsUnknownODataAnnotationName(annotationName.Span)) { annotationValue = await ReadODataOrCustomInstanceAnnotationValueAsync(annotationName) .ConfigureAwait(false); @@ -1199,7 +1236,7 @@ private string ReadPayloadStartImplementation( /// A task that represents the asynchronous read operation. /// The value of the TResult parameter contains the annotation value. /// - private async Task ReadODataOrCustomInstanceAnnotationValueAsync(string annotationName) + private async Task ReadODataOrCustomInstanceAnnotationValueAsync(ReadOnlyMemory annotationName) { // Read over the name. await this.JsonReader.ReadAsync() @@ -1244,25 +1281,25 @@ await this.JsonReader.SkipValueAsync() /// 7). The first component contains EndOfObject if end of the object scope was reached and no properties are to be reported, while the second component contains null. /// This can only happen if there's a property annotation which is ignored (for example custom one) at the end of the object. /// - private async Task<(PropertyParsingResult ParsingResult, string PropertyName)> ParsePropertyAsync( + private async Task<(PropertyParsingResult ParsingResult, ReadOnlyMemory PropertyName)> ParsePropertyAsync( PropertyAndAnnotationCollector propertyAndAnnotationCollector, Func> readPropertyAnnotationValueDelegate) { Debug.Assert(propertyAndAnnotationCollector != null, $"{nameof(propertyAndAnnotationCollector)} != null"); Debug.Assert(readPropertyAnnotationValueDelegate != null, $"{nameof(readPropertyAnnotationValueDelegate)} != null"); - string lastPropertyAnnotationNameFound = null; - string parsedPropertyName = null; + ReadOnlyMemory lastPropertyAnnotationNameFound = null; + ReadOnlyMemory parsedPropertyName = null; while (this.JsonReader.NodeType == JsonNodeType.Property) { - string nameFromReader = await this.JsonReader.GetPropertyNameAsync() + ReadOnlyMemory nameFromReader = await this.JsonReader.GetPropertyNameAsync() .ConfigureAwait(false); - string propertyNameFromReader, annotationNameFromReader; + ReadOnlyMemory propertyNameFromReader, annotationNameFromReader; bool isPropertyAnnotation = TryParsePropertyAnnotation(nameFromReader, out propertyNameFromReader, out annotationNameFromReader); // Reading a nested delta resource set - if (isPropertyAnnotation && string.Equals(this.CompleteSimplifiedODataAnnotation(annotationNameFromReader), ODataAnnotationNames.ODataDelta, StringComparison.Ordinal)) + if (isPropertyAnnotation && this.CompleteSimplifiedODataAnnotation(annotationNameFromReader.Span).SequenceEqual(ODataAnnotationNames.ODataDelta)) { // Read over the property name. await this.JsonReader.ReadAsync() @@ -1277,15 +1314,15 @@ await this.JsonReader.ReadAsync() // annotation (if necessary) by prepending it with "odata." if (!isPropertyAnnotation) { - isInstanceAnnotation = IsInstanceAnnotation(nameFromReader); - propertyNameFromReader = isInstanceAnnotation ? this.CompleteSimplifiedODataAnnotation(nameFromReader.Substring(1)) : nameFromReader; + isInstanceAnnotation = IsInstanceAnnotation(nameFromReader.Span); + propertyNameFromReader = isInstanceAnnotation ? this.CompleteSimplifiedODataAnnotation(nameFromReader.Slice(1)) : nameFromReader; } // If parsedPropertyName is set and is different from the property name the reader is currently on, // we have parsed a property annotation for a different property than the one at the current position. - if (parsedPropertyName != null && !string.Equals(parsedPropertyName, propertyNameFromReader, StringComparison.Ordinal)) + if (!parsedPropertyName.IsEmpty && !parsedPropertyName.Equals(propertyNameFromReader)) { - if (ODataJsonReaderUtils.IsAnnotationProperty(parsedPropertyName)) + if (ODataJsonReaderUtils.IsAnnotationProperty(parsedPropertyName.Span)) { throw new ODataException(Error.Format(SRResources.ODataJsonDeserializer_AnnotationTargetingInstanceAnnotationWithoutValue, lastPropertyAnnotationNameFound, @@ -1305,14 +1342,14 @@ await this.JsonReader.ReadAsync() // Note that we don't skip over unknown odata annotations targeting another annotation. // We don't allow annotations (except odata.type) targeting other annotations, // so ProcessPropertyAnnotationAsync() will test and fail for that case. - if (!ODataJsonReaderUtils.IsAnnotationProperty(propertyNameFromReader)) + if (!ODataJsonReaderUtils.IsAnnotationProperty(propertyNameFromReader.Span)) { (bool isUnknownODataAnnotationName, object tempAnnotationValue) = await this.SkipOverUnknownODataAnnotationAsync( annotationNameFromReader).ConfigureAwait(false); if (isUnknownODataAnnotationName) { annotationValue = tempAnnotationValue; - propertyAndAnnotationCollector.AddODataPropertyAnnotation(propertyNameFromReader, annotationNameFromReader, annotationValue); + propertyAndAnnotationCollector.AddODataPropertyAnnotation(propertyNameFromReader.ToString(), annotationNameFromReader.ToString(), annotationValue); continue; } } @@ -1339,8 +1376,8 @@ await this.ProcessPropertyAnnotationAsync( annotationValue = tempAnnotationValue; // collect 'odata.' annotation: // here we know the original property name contains no '@', but '.' dot - Debug.Assert(annotationNameFromReader == null, $"{nameof(annotationNameFromReader)} == null"); - propertyAndAnnotationCollector.AddODataScopeAnnotation(propertyNameFromReader, annotationValue); + Debug.Assert(annotationNameFromReader.IsEmpty, $"{nameof(annotationNameFromReader)} == null"); + propertyAndAnnotationCollector.AddODataScopeAnnotation(propertyNameFromReader.ToString(), annotationValue); continue; } } @@ -1351,12 +1388,12 @@ await this.ProcessPropertyAnnotationAsync( // call this.JsonReader.ReadAsync() as appropriate to read past the property name. parsedPropertyName = propertyNameFromReader; - if (!isInstanceAnnotation && ODataJsonUtils.IsMetadataReferenceProperty(propertyNameFromReader)) + if (!isInstanceAnnotation && ODataJsonUtils.IsMetadataReferenceProperty(propertyNameFromReader.Span)) { return (PropertyParsingResult.MetadataReferenceProperty, parsedPropertyName); } - if (!isInstanceAnnotation && !ODataJsonReaderUtils.IsAnnotationProperty(propertyNameFromReader)) + if (!isInstanceAnnotation && !ODataJsonReaderUtils.IsAnnotationProperty(propertyNameFromReader.Span)) { // Normal property return (PropertyParsingResult.PropertyWithValue, parsedPropertyName); @@ -1364,10 +1401,10 @@ await this.ProcessPropertyAnnotationAsync( // collect 'xxx.yyyy' annotation: // here we know the original property name contains no '@', but '.' dot - Debug.Assert(annotationNameFromReader == null, $"{nameof(annotationNameFromReader)} == null"); + Debug.Assert(annotationNameFromReader.IsEmpty, $"{nameof(annotationNameFromReader)} == null"); // Handle 'odata.XXXXX' annotations - if (isInstanceAnnotation && ODataJsonReaderUtils.IsODataAnnotationName(propertyNameFromReader)) + if (isInstanceAnnotation && ODataJsonReaderUtils.IsODataAnnotationName(propertyNameFromReader.Span)) { return (PropertyParsingResult.ODataInstanceAnnotation, parsedPropertyName); } @@ -1377,9 +1414,9 @@ await this.ProcessPropertyAnnotationAsync( } this.AssertJsonCondition(JsonNodeType.EndObject); - if (parsedPropertyName != null) + if (!parsedPropertyName.IsEmpty) { - if (ODataJsonReaderUtils.IsAnnotationProperty(parsedPropertyName)) + if (ODataJsonReaderUtils.IsAnnotationProperty(parsedPropertyName.Span)) { throw new ODataException( Error.Format(SRResources.ODataJsonDeserializer_AnnotationTargetingInstanceAnnotationWithoutValue, @@ -1402,14 +1439,14 @@ await this.ProcessPropertyAnnotationAsync( /// Delegate to read the property annotation value. /// A task that represents the asynchronous read operation. private Task ProcessPropertyAnnotationAsync( - string annotatedPropertyName, - string annotationName, + ReadOnlyMemory annotatedPropertyName, + ReadOnlyMemory annotationName, PropertyAndAnnotationCollector propertyAndAnnotationCollector, Func> readPropertyAnnotationValueDelegate) { // We don't currently support annotation targeting an instance annotation except for the @odata.type property annotation. - if (ODataJsonReaderUtils.IsAnnotationProperty(annotatedPropertyName) - && !string.Equals(annotationName, ODataAnnotationNames.ODataType, StringComparison.Ordinal)) + if (ODataJsonReaderUtils.IsAnnotationProperty(annotatedPropertyName.Span) + && !annotatedPropertyName.Equals(ODataAnnotationNames.ODataType)) { return TaskUtils.GetFaultedTask( new ODataException(Error.Format(SRResources.ODataJsonDeserializer_OnlyODataTypeAnnotationCanTargetInstanceAnnotation, @@ -1456,36 +1493,38 @@ internal Task ReadPayloadEndAsync(bool isReadingNestedPayload) /// Delegate to read the property annotation value. /// A task that represents the asynchronous read operation. private async Task ReadODataOrCustomInstanceAnnotationValueAsync( - string annotatedPropertyName, - string annotationName, + ReadOnlyMemory annotatedPropertyName, + ReadOnlyMemory annotationName, PropertyAndAnnotationCollector propertyAndAnnotationCollector, Func> readPropertyAnnotationValueDelegate) { // Read over the property name. await this.JsonReader.ReadAsync() .ConfigureAwait(false); - if (ODataJsonReaderUtils.IsODataAnnotationName(annotationName)) + if (ODataJsonReaderUtils.IsODataAnnotationName(annotationName.Span)) { + string annotationNameStr = annotationName.Span.ToString(); // OData annotations are read - object propertyAnnotationValue = await readPropertyAnnotationValueDelegate(annotationName) + object propertyAnnotationValue = await readPropertyAnnotationValueDelegate(annotationNameStr) .ConfigureAwait(false); - propertyAndAnnotationCollector.AddODataPropertyAnnotation(annotatedPropertyName, annotationName, propertyAnnotationValue); + propertyAndAnnotationCollector.AddODataPropertyAnnotation(annotatedPropertyName.Span.ToString(), annotationNameStr, propertyAnnotationValue); } else { - if (this.ShouldSkipCustomInstanceAnnotation(annotationName) + if (this.ShouldSkipCustomInstanceAnnotation(annotationName.Span) || (this is ODataJsonErrorDeserializer && this.MessageReaderSettings.ShouldIncludeAnnotation == null)) { - propertyAndAnnotationCollector.CheckIfPropertyOpenForAnnotations(annotatedPropertyName, annotationName); + propertyAndAnnotationCollector.CheckIfPropertyOpenForAnnotations(annotatedPropertyName.Span.ToString(), annotationName.Span.ToString()); await this.JsonReader.SkipValueAsync() .ConfigureAwait(false); } else { Debug.Assert(this.ReadPropertyCustomAnnotationValueAsync != null, $"{nameof(ReadPropertyCustomAnnotationValueAsync)} != null"); - object propertyCustomAnnotationValue = await this.ReadPropertyCustomAnnotationValueAsync(propertyAndAnnotationCollector, annotationName) + string annotationNameString = annotationName.Span.ToString(); + object propertyCustomAnnotationValue = await this.ReadPropertyCustomAnnotationValueAsync(propertyAndAnnotationCollector, annotationNameString) .ConfigureAwait(false); - propertyAndAnnotationCollector.AddCustomPropertyAnnotation(annotatedPropertyName, annotationName, propertyCustomAnnotationValue); + propertyAndAnnotationCollector.AddCustomPropertyAnnotation(annotatedPropertyName.ToString(), annotationNameString, propertyCustomAnnotationValue); } } } diff --git a/src/Microsoft.OData.Core/Json/ODataJsonErrorDeserializer.cs b/src/Microsoft.OData.Core/Json/ODataJsonErrorDeserializer.cs index 05f9090aa1..2f5768b352 100644 --- a/src/Microsoft.OData.Core/Json/ODataJsonErrorDeserializer.cs +++ b/src/Microsoft.OData.Core/Json/ODataJsonErrorDeserializer.cs @@ -127,11 +127,11 @@ private ODataError ReadTopLevelErrorImplementation() while (this.JsonReader.NodeType == JsonNodeType.Property) { - string propertyName = this.JsonReader.ReadPropertyName(); - if (!string.Equals(ODataJsonConstants.ODataErrorPropertyName, propertyName, StringComparison.Ordinal)) + ReadOnlySpan propertyName = this.JsonReader.ReadPropertyName(); + if (!propertyName.SequenceEqual(ODataJsonConstants.ODataErrorPropertyName.AsSpan())) { // we only allow a single 'error' property for a top-level error object - throw new ODataException(Error.Format(SRResources.ODataJsonErrorDeserializer_TopLevelErrorWithInvalidProperty, propertyName)); + throw new ODataException(Error.Format(SRResources.ODataJsonErrorDeserializer_TopLevelErrorWithInvalidProperty, propertyName.ToString())); } if (error != null) @@ -491,10 +491,10 @@ private async Task ReadTopLevelErrorImplementationAsync() while (this.JsonReader.NodeType == JsonNodeType.Property) { - string propertyName = await this.JsonReader.ReadPropertyNameAsync() + ReadOnlyMemory propertyName = await this.JsonReader.ReadPropertyNameAsync() .ConfigureAwait(false); - if (!string.Equals(ODataJsonConstants.ODataErrorPropertyName, propertyName, StringComparison.Ordinal)) + if (!propertyName.Equals(ODataJsonConstants.ODataErrorPropertyName)) { // We only allow a single 'error' property for a top-level error object throw new ODataException(Error.Format(SRResources.ODataJsonErrorDeserializer_TopLevelErrorWithInvalidProperty, propertyName)); diff --git a/src/Microsoft.OData.Core/Json/ODataJsonPayloadKindDetectionDeserializer.cs b/src/Microsoft.OData.Core/Json/ODataJsonPayloadKindDetectionDeserializer.cs index d3dba318bb..ffbee7061b 100644 --- a/src/Microsoft.OData.Core/Json/ODataJsonPayloadKindDetectionDeserializer.cs +++ b/src/Microsoft.OData.Core/Json/ODataJsonPayloadKindDetectionDeserializer.cs @@ -122,8 +122,8 @@ private IEnumerable DetectPayloadKindImplementation(ODataPaylo ODataError error = null; while (this.JsonReader.NodeType == JsonNodeType.Property) { - string propertyName = this.JsonReader.ReadPropertyName(); - string annotatedPropertyName, annotationName; + ReadOnlySpan propertyName = this.JsonReader.ReadPropertyName(); + ReadOnlySpan annotatedPropertyName, annotationName; if (!ODataJsonDeserializer.TryParsePropertyAnnotation(propertyName, out annotatedPropertyName, out annotationName)) { if (ODataJsonReaderUtils.IsAnnotationProperty(propertyName)) @@ -141,7 +141,7 @@ private IEnumerable DetectPayloadKindImplementation(ODataPaylo } else { - if (string.Equals(ODataJsonConstants.ODataErrorPropertyName, propertyName, StringComparison.Ordinal)) + if (propertyName.SequenceEqual(ODataJsonConstants.ODataErrorPropertyName.AsSpan())) { // If we find multiple errors or an invalid error value, this is not an error payload. if (error != null || !this.JsonReader.StartBufferingAndTryToReadInStreamErrorPropertyValue(out error)) @@ -203,13 +203,13 @@ private async Task> DetectPayloadKindImplementatio ODataError error = null; while (this.JsonReader.NodeType == JsonNodeType.Property) { - string propertyName = await this.JsonReader.ReadPropertyNameAsync() + ReadOnlyMemory propertyName = await this.JsonReader.ReadPropertyNameAsync() .ConfigureAwait(false); if (!TryParsePropertyAnnotation(propertyName, out _, out _)) { - if (ODataJsonReaderUtils.IsAnnotationProperty(propertyName)) + if (ODataJsonReaderUtils.IsAnnotationProperty(propertyName.Span)) { - if (propertyName != null && propertyName.StartsWith(ODataJsonConstants.ODataPropertyAnnotationSeparatorChar + ODataJsonConstants.ODataAnnotationNamespacePrefix, StringComparison.Ordinal)) + if (!propertyName.IsEmpty && propertyName.Span.StartsWith((ODataJsonConstants.ODataPropertyAnnotationSeparatorChar + ODataJsonConstants.ODataAnnotationNamespacePrefix).AsSpan(), StringComparison.Ordinal)) { // Any @odata.* instance annotations are not allowed for errors. return Enumerable.Empty(); @@ -223,7 +223,7 @@ await this.JsonReader.SkipValueAsync() } else { - if (string.Equals(ODataJsonConstants.ODataErrorPropertyName, propertyName, StringComparison.Ordinal)) + if (propertyName.Equals(ODataJsonConstants.ODataErrorPropertyName)) { // If we find multiple errors or an invalid error value, this is not an error payload. if (error != null) diff --git a/src/Microsoft.OData.Core/Json/ODataJsonPropertyAndValueDeserializer.cs b/src/Microsoft.OData.Core/Json/ODataJsonPropertyAndValueDeserializer.cs index 56d5ca15ba..410221861c 100644 --- a/src/Microsoft.OData.Core/Json/ODataJsonPropertyAndValueDeserializer.cs +++ b/src/Microsoft.OData.Core/Json/ODataJsonPropertyAndValueDeserializer.cs @@ -1271,9 +1271,9 @@ private bool TryReadODataTypeAnnotation(out string payloadTypeName) payloadTypeName = null; bool result = false; - string propertyName = this.JsonReader.GetPropertyName(); - if (string.Equals(propertyName, ODataJsonConstants.PrefixedODataTypePropertyName, StringComparison.Ordinal) - || this.CompareSimplifiedODataAnnotation(ODataJsonConstants.SimplifiedODataTypePropertyName, propertyName)) + ReadOnlySpan propertyName = this.JsonReader.GetPropertyName(); + if (propertyName.SequenceEqual(ODataJsonConstants.PrefixedODataTypePropertyName) + || this.CompareSimplifiedODataAnnotation(ODataJsonConstants.SimplifiedODataTypePropertyName.AsSpan(), propertyName)) { // Read over the property name this.JsonReader.ReadNext(); @@ -2166,7 +2166,7 @@ private bool ReadingResourceProperty(PropertyAndAnnotationCollector propertyAndA /// If the method detects the odata.null annotation, it will read it; otherwise the reader does not move. private bool IsTopLevel6xNullValue() { - bool odataNullAnnotationInPayload = this.JsonReader.NodeType == JsonNodeType.Property && string.Equals(ODataJsonConstants.PrefixedODataNullPropertyName, JsonReader.GetPropertyName(), StringComparison.Ordinal); + bool odataNullAnnotationInPayload = this.JsonReader.NodeType == JsonNodeType.Property && JsonReader.GetPropertyName().SequenceEqual(ODataJsonConstants.PrefixedODataNullPropertyName); if (odataNullAnnotationInPayload) { // If we found the expected annotation read over the property name @@ -2453,14 +2453,14 @@ protected static async Task ValidateExpandedNestedResourceInfoPropertyValueAsync /// Post-Condition: JsonNodeType.Property - the next property after the annotation /// JsonNodeType.EndObject - end of the parent object /// - protected async Task ReadODataTypeAnnotationValueAsync() + protected async Task> ReadODataTypeAnnotationValueAsync() { this.AssertJsonCondition(JsonNodeType.PrimitiveValue, JsonNodeType.StartObject, JsonNodeType.StartArray); - string typeName = ReaderUtils.AddEdmPrefixOfTypeName( + ReadOnlyMemory typeName = ReaderUtils.AddEdmPrefixOfTypeName( ReaderUtils.RemovePrefixOfTypeName( await this.JsonReader.ReadStringValueAsync().ConfigureAwait(false))); - if (typeName == null) + if (typeName.IsEmpty) { // TODO: It's meaningless to output an error message using "typeName == null". Should use the original JSON value to construct the error message. throw new ODataException(Error.Format(SRResources.ODataJsonPropertyAndValueDeserializer_InvalidTypeName, typeName)); @@ -2549,16 +2549,16 @@ protected async Task ReadTypePropertyAnnotationValueAsync(string propert /// Post-Condition: JsonNodeType.Property - the next property after the annotation or if the reader did not move /// JsonNodeType.EndObject - end of the parent object /// - private async Task<(bool IsReadSuccessfully, string PayloadTypeName)> TryReadODataTypeAnnotationAsync() + private async Task<(bool IsReadSuccessfully, ReadOnlyMemory PayloadTypeName)> TryReadODataTypeAnnotationAsync() { this.AssertJsonCondition(JsonNodeType.Property); - string payloadTypeName = null; + ReadOnlyMemory payloadTypeName = null; bool result = false; - string propertyName = await this.JsonReader.GetPropertyNameAsync() + ReadOnlyMemory propertyName = await this.JsonReader.GetPropertyNameAsync() .ConfigureAwait(false); - if (string.Equals(propertyName, ODataJsonConstants.PrefixedODataTypePropertyName, StringComparison.Ordinal) - || this.CompareSimplifiedODataAnnotation(ODataJsonConstants.SimplifiedODataTypePropertyName, propertyName)) + if (propertyName.Equals(ODataJsonConstants.PrefixedODataTypePropertyName) + || this.CompareSimplifiedODataAnnotation(ODataJsonConstants.SimplifiedODataTypePropertyName.AsSpan(), propertyName.Span)) { // Read over the property name await this.JsonReader.ReadNextAsync() diff --git a/src/Microsoft.OData.Core/Json/ODataJsonReaderUtils.cs b/src/Microsoft.OData.Core/Json/ODataJsonReaderUtils.cs index 8cf216c719..e851f81fbc 100644 --- a/src/Microsoft.OData.Core/Json/ODataJsonReaderUtils.cs +++ b/src/Microsoft.OData.Core/Json/ODataJsonReaderUtils.cs @@ -142,11 +142,11 @@ internal static void EnsureInstance(ref T instance) /// /// The property name to test. /// true if the property name is an OData annotation property name, false otherwise. - internal static bool IsODataAnnotationName(string propertyName) + internal static bool IsODataAnnotationName(ReadOnlySpan propertyName) { - Debug.Assert(!string.IsNullOrEmpty(propertyName), "!string.IsNullOrEmpty(propertyName)"); + Debug.Assert(!propertyName.IsEmpty, "!string.IsNullOrEmpty(propertyName)"); - return propertyName.StartsWith(ODataJsonConstants.ODataAnnotationNamespacePrefix, StringComparison.Ordinal); + return propertyName.StartsWith(ODataJsonConstants.ODataAnnotationNamespacePrefix.AsSpan(), StringComparison.Ordinal); } /// @@ -157,11 +157,11 @@ internal static bool IsODataAnnotationName(string propertyName) /// /// This method returns true both for normal annotation as well as property annotations. /// - internal static bool IsAnnotationProperty(string propertyName) + internal static bool IsAnnotationProperty(ReadOnlySpan propertyName) { - Debug.Assert(!string.IsNullOrEmpty(propertyName), "!string.IsNullOrEmpty(propertyName)"); + Debug.Assert(!propertyName.IsEmpty, "!string.IsNullOrEmpty(propertyName)"); - return propertyName.IndexOf('.', StringComparison.Ordinal) >= 0; + return propertyName.IndexOf('.') >= 0; } /// diff --git a/src/Microsoft.OData.Core/Json/ODataJsonUtils.cs b/src/Microsoft.OData.Core/Json/ODataJsonUtils.cs index 3adf313189..18466d06a2 100644 --- a/src/Microsoft.OData.Core/Json/ODataJsonUtils.cs +++ b/src/Microsoft.OData.Core/Json/ODataJsonUtils.cs @@ -28,11 +28,11 @@ internal static class ODataJsonUtils /// /// The name of the property. /// true if is a name of a metadata reference property, false otherwise. - internal static bool IsMetadataReferenceProperty(string propertyName) + internal static bool IsMetadataReferenceProperty(ReadOnlySpan propertyName) { - Debug.Assert(!String.IsNullOrEmpty(propertyName), "!string.IsNullOrEmpty(propertyName)"); + Debug.Assert(!propertyName.IsEmpty, "!string.IsNullOrEmpty(propertyName)"); - return propertyName.IndexOf(ODataConstants.ContextUriFragmentIndicator, StringComparison.Ordinal) >= 0; + return propertyName.IndexOf(ODataConstants.ContextUriFragmentIndicator) >= 0; } /// diff --git a/src/Microsoft.OData.Core/Metadata/EdmLibraryExtensions.cs b/src/Microsoft.OData.Core/Metadata/EdmLibraryExtensions.cs index 33434a40f0..8c58145bf8 100644 --- a/src/Microsoft.OData.Core/Metadata/EdmLibraryExtensions.cs +++ b/src/Microsoft.OData.Core/Metadata/EdmLibraryExtensions.cs @@ -559,11 +559,11 @@ internal static string FullNameWithNonBindingParameters(this IEdmOperation opera /// /// Operation import in question. /// Name of the operation import with parameters. - internal static string NameWithParameters(this IEdmOperationImport operationImport) + internal static ReadOnlySpan NameWithParameters(this IEdmOperationImport operationImport) { Debug.Assert(operationImport != null, "operationImport != null"); - return operationImport.Name + operationImport.ParameterTypesToString(); + return (operationImport.Name + operationImport.ParameterTypesToString().ToString()).AsSpan(); } /// @@ -571,11 +571,11 @@ internal static string NameWithParameters(this IEdmOperationImport operationImpo /// /// Operation import in question. /// Full name of the operation import with parameters. - internal static string FullNameWithParameters(this IEdmOperationImport operationImport) + internal static ReadOnlySpan FullNameWithParameters(this IEdmOperationImport operationImport) { Debug.Assert(operationImport != null, "operationImport != null"); - return operationImport.FullName() + operationImport.ParameterTypesToString(); + return (operationImport.FullName() + operationImport.ParameterTypesToString().ToString()).AsSpan(); } /// @@ -1065,7 +1065,7 @@ internal static IEdmCollectionType GetCollectionType(IEdmTypeReference itemTypeR /// /// CollectionValue type name read from payload. /// CollectionValue element type name or null if not a collectionValue. - internal static string GetCollectionItemTypeName(string typeName) + internal static ReadOnlySpan GetCollectionItemTypeName(ReadOnlySpan typeName) { return GetCollectionItemTypeName(typeName, false); } @@ -1075,17 +1075,17 @@ internal static string GetCollectionItemTypeName(string typeName) /// /// The original collection type string. /// The full type name for the given origin type name/>. - internal static string GetCollectionTypeFullName(string typeName) + internal static ReadOnlySpan GetCollectionTypeFullName(ReadOnlySpan typeName) { - if (typeName != null) + if (!typeName.IsEmpty) { - string innerTypeName = GetCollectionItemTypeName(typeName); + ReadOnlySpan innerTypeName = GetCollectionItemTypeName(typeName); if (innerTypeName != null) { - IEdmSchemaType primitiveType = EdmCoreModel.Instance.FindDeclaredType(innerTypeName); + IEdmSchemaType primitiveType = EdmCoreModel.Instance.FindDeclaredType(innerTypeName.ToString()); if (primitiveType != null) { - return GetCollectionTypeName(primitiveType.FullName()); + return GetCollectionTypeName(primitiveType.FullName()).AsSpan(); } } } @@ -1883,14 +1883,16 @@ private static string NonBindingParameterNamesToString(this IEdmOperation operat /// - "Collection (Edm.Int32)" /// - "Collection(" /// - private static string GetCollectionItemTypeName(string typeName, bool isNested) + private static ReadOnlySpan GetCollectionItemTypeName(ReadOnlySpan typeName, bool isNested) { int collectionTypeQualifierLength = CollectionTypeQualifier.Length; // to be recognized as a collection wireTypeName must not be null, has to start with "Collection(" and end with ")" and must not be "Collection()" - if (typeName != null && - typeName.StartsWith(CollectionTypeQualifier + "(", StringComparison.Ordinal) && - typeName[typeName.Length - 1] == ')' && + if (!typeName.IsEmpty && + typeName.Length > collectionTypeQualifierLength + 1 && + typeName.StartsWith(CollectionTypeQualifier, StringComparison.Ordinal) && + typeName[collectionTypeQualifierLength] == '(' && + typeName[^1] == ')' && typeName.Length != collectionTypeQualifierLength + 2) { if (isNested) @@ -1898,7 +1900,7 @@ private static string GetCollectionItemTypeName(string typeName, bool isNested) throw new ODataException(SRResources.ValidationUtils_NestedCollectionsAreNotSupported); } - string innerTypeName = typeName.Substring(collectionTypeQualifierLength + 1, typeName.Length - (collectionTypeQualifierLength + 2)); + ReadOnlySpan innerTypeName = typeName.Slice(collectionTypeQualifierLength + 1, typeName.Length - (collectionTypeQualifierLength + 2)); // Check if it is not a nested collection and throw if it is GetCollectionItemTypeName(innerTypeName, true); @@ -1914,12 +1916,14 @@ private static string GetCollectionItemTypeName(string typeName, bool isNested) /// /// Function import in question. /// Comma separated operation import parameter types enclosed in parentheses. - private static string ParameterTypesToString(this IEdmOperationImport operationImport) + private static ReadOnlySpan ParameterTypesToString(this IEdmOperationImport operationImport) { // TODO: Resolve duplication of operationImport and operation - return ODataJsonConstants.FunctionParameterStart + + string result = ODataJsonConstants.FunctionParameterStart + string.Join(ODataJsonConstants.FunctionParameterSeparator, operationImport.Operation.Parameters.Select(p => p.Type.FullName()).ToArray()) + ODataJsonConstants.FunctionParameterEnd; + + return result.AsSpan(); } /// diff --git a/src/Microsoft.OData.Core/ReaderUtils.cs b/src/Microsoft.OData.Core/ReaderUtils.cs index 74dc855636..be83a4b6b8 100644 --- a/src/Microsoft.OData.Core/ReaderUtils.cs +++ b/src/Microsoft.OData.Core/ReaderUtils.cs @@ -139,17 +139,16 @@ internal static string GetExpectedPropertyName(IEdmStructuralProperty expectedPr /// /// The type name which may be prefixed (#). /// The type name with prefix removed, if there is. - internal static string RemovePrefixOfTypeName(string typeName) + internal static ReadOnlySpan RemovePrefixOfTypeName(ReadOnlySpan typeName) { - string prefixRemovedTypeName = typeName; - if (!string.IsNullOrEmpty(typeName) && typeName.StartsWith(ODataConstants.TypeNamePrefix, StringComparison.Ordinal)) + if (!typeName.IsEmpty && typeName.StartsWith(ODataConstants.TypeNamePrefix.AsSpan(), StringComparison.Ordinal)) { - prefixRemovedTypeName = typeName.Substring(ODataConstants.TypeNamePrefix.Length); + typeName = typeName.Slice(ODataConstants.TypeNamePrefix.Length); - Debug.Assert(!prefixRemovedTypeName.StartsWith(ODataConstants.TypeNamePrefix, StringComparison.Ordinal), "The type name not start with " + ODataConstants.TypeNamePrefix + "after removing prefix"); + Debug.Assert(!typeName.StartsWith(ODataConstants.TypeNamePrefix, StringComparison.Ordinal), "The type name not start with " + ODataConstants.TypeNamePrefix + "after removing prefix"); } - return prefixRemovedTypeName; + return typeName; } /// @@ -157,9 +156,9 @@ internal static string RemovePrefixOfTypeName(string typeName) /// /// The type name which may be not prefixed (Edm.). /// The type name with Edm. prefix - internal static string AddEdmPrefixOfTypeName(string typeName) + internal static ReadOnlySpan AddEdmPrefixOfTypeName(ReadOnlySpan typeName) { - if (!string.IsNullOrEmpty(typeName)) + if (!typeName.IsEmpty) { string itemTypeName = EdmLibraryExtensions.GetCollectionItemTypeName(typeName); if (itemTypeName == null)