diff --git a/src/LanguageServer/Protocol/Protocol/Internal/Converters/ImageElementConverter.cs b/src/LanguageServer/Protocol/Protocol/Internal/Converters/ImageElementConverter.cs index 188f872aa0619..17a3e47a4b5f2 100644 --- a/src/LanguageServer/Protocol/Protocol/Internal/Converters/ImageElementConverter.cs +++ b/src/LanguageServer/Protocol/Protocol/Internal/Converters/ImageElementConverter.cs @@ -9,6 +9,7 @@ using Roslyn.Text.Adornments; namespace Roslyn.LanguageServer.Protocol; + internal sealed class ImageElementConverter : JsonConverter { public static readonly ImageElementConverter Instance = new(); @@ -20,6 +21,8 @@ public override ImageElement Read(ref Utf8JsonReader reader, Type typeToConvert, ImageId? imageId = null; string? automationName = null; + Span scratchChars = stackalloc char[64]; + while (reader.Read()) { if (reader.TokenType == JsonTokenType.EndObject) @@ -30,7 +33,11 @@ public override ImageElement Read(ref Utf8JsonReader reader, Type typeToConvert, if (reader.TokenType == JsonTokenType.PropertyName) { - var propertyName = reader.GetString(); + var valueLength = reader.HasValueSequence ? reader.ValueSequence.Length : reader.ValueSpan.Length; + + var propertyNameLength = valueLength <= scratchChars.Length ? reader.CopyString(scratchChars) : -1; + var propertyName = propertyNameLength >= 0 ? scratchChars[..propertyNameLength] : reader.GetString().AsSpan(); + reader.Read(); switch (propertyName) { @@ -41,7 +48,10 @@ public override ImageElement Read(ref Utf8JsonReader reader, Type typeToConvert, automationName = reader.GetString(); break; case ObjectContentConverter.TypeProperty: - if (reader.GetString() != nameof(ImageElement)) + var typePropertyLength = valueLength <= scratchChars.Length ? reader.CopyString(scratchChars) : -1; + var typeProperty = typePropertyLength >= 0 ? scratchChars[..typePropertyLength] : reader.GetString().AsSpan(); + + if (!typeProperty.SequenceEqual(nameof(ImageElement).AsSpan())) throw new JsonException($"Expected {ObjectContentConverter.TypeProperty} property value {nameof(ImageElement)}"); break; default: @@ -60,7 +70,10 @@ public override void Write(Utf8JsonWriter writer, ImageElement value, JsonSerial writer.WriteStartObject(); writer.WritePropertyName(nameof(ImageElement.ImageId)); ImageIdConverter.Instance.Write(writer, value.ImageId, options); - writer.WriteString(nameof(ImageElement.AutomationName), value.AutomationName); + + if (value.AutomationName != null) + writer.WriteString(nameof(ImageElement.AutomationName), value.AutomationName); + writer.WriteString(ObjectContentConverter.TypeProperty, nameof(ImageElement)); writer.WriteEndObject(); } diff --git a/src/LanguageServer/Protocol/Protocol/Internal/Converters/ImageIdConverter.cs b/src/LanguageServer/Protocol/Protocol/Internal/Converters/ImageIdConverter.cs index b2d917c99b228..6df915efa4eb1 100644 --- a/src/LanguageServer/Protocol/Protocol/Internal/Converters/ImageIdConverter.cs +++ b/src/LanguageServer/Protocol/Protocol/Internal/Converters/ImageIdConverter.cs @@ -8,6 +8,7 @@ using Roslyn.Core.Imaging; namespace Roslyn.LanguageServer.Protocol; + internal sealed class ImageIdConverter : JsonConverter { public static readonly ImageIdConverter Instance = new(); @@ -16,21 +17,53 @@ public override ImageId Read(ref Utf8JsonReader reader, Type objectType, JsonSer { if (reader.TokenType == JsonTokenType.StartObject) { - using var document = JsonDocument.ParseValue(ref reader); - var root = document.RootElement; - if (root.TryGetProperty(ObjectContentConverter.TypeProperty, out var typeProperty) && typeProperty.GetString() != nameof(ImageId)) + Guid? guid = null; + int? id = null; + + Span scratchChars = stackalloc char[64]; + + while (reader.Read()) { - throw new JsonException($"Expected {ObjectContentConverter.TypeProperty} property value {nameof(ImageId)}"); - } + if (reader.TokenType == JsonTokenType.EndObject) + { + if (guid is null || id is null) + throw new JsonException("Expected properties Guid and Id to be present"); - var guid = root.GetProperty(nameof(ImageId.Guid)).GetString() ?? throw new JsonException(); - var id = root.GetProperty(nameof(ImageId.Id)).GetInt32(); - return new ImageId(new Guid(guid), id); - } - else - { - throw new JsonException("Expected start object or null tokens"); + return new ImageId(guid.Value, id.Value); + } + + if (reader.TokenType == JsonTokenType.PropertyName) + { + var valueLength = reader.HasValueSequence ? reader.ValueSequence.Length : reader.ValueSpan.Length; + + var propertyNameLength = valueLength <= scratchChars.Length ? reader.CopyString(scratchChars) : -1; + var propertyName = propertyNameLength >= 0 ? scratchChars[..propertyNameLength] : reader.GetString().AsSpan(); + + reader.Read(); + switch (propertyName) + { + case nameof(ImageId.Guid): + guid = reader.GetGuid(); + break; + case nameof(ImageId.Id): + id = reader.GetInt32(); + break; + case ObjectContentConverter.TypeProperty: + var typePropertyLength = valueLength <= scratchChars.Length ? reader.CopyString(scratchChars) : -1; + var typeProperty = typePropertyLength >= 0 ? scratchChars[..typePropertyLength] : reader.GetString().AsSpan(); + + if (!typeProperty.SequenceEqual(nameof(ImageId).AsSpan())) + throw new JsonException($"Expected {ObjectContentConverter.TypeProperty} property value {nameof(ImageId)}"); + break; + default: + reader.Skip(); + break; + } + } + } } + + throw new JsonException("Expected start object or null tokens"); } public override void Write(Utf8JsonWriter writer, ImageId value, JsonSerializerOptions options)