File: Protocol\Internal\Converters\ObjectContentConverter.cs
Web Access
Project: src\src\LanguageServer\Protocol\Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj (Microsoft.CodeAnalysis.LanguageServer.Protocol)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
using Roslyn.Core.Imaging;
using Roslyn.Text.Adornments;
 
namespace Roslyn.LanguageServer.Protocol;
 
/// <summary>
/// Object Content converter used to serialize and deserialize Text and Adornements from VS.
///
/// This converts the following types:
/// <list type="bullet">
/// <item><description><see cref="ImageId"/></description></item>,
/// <item><description><see cref="ImageElement"/></description></item>,
/// <item><description><see cref="ContainerElement"/></description></item>,
/// <item><description><see cref="ClassifiedTextElement"/></description></item>,
/// <item><description><see cref="ClassifiedTextRun"/></description></item>.
/// </list>
/// Every other type is serialized as a string using the <see cref="object.ToString()"/> method.
/// </summary>
internal class ObjectContentConverter : JsonConverter<object>
{
    /// <summary>
    /// The property name used to save the .NET Type name of the serialized object.
    /// </summary>
    public const string TypeProperty = "_vs_type";
 
    /// <summary>
    /// A reusable instance of the <see cref="ObjectContentConverter"/>.
    /// </summary>
    public static readonly ObjectContentConverter Instance = new();
 
    public override object? Read(ref Utf8JsonReader reader, Type objectType, JsonSerializerOptions options)
    {
        if (reader.TokenType == JsonTokenType.Null)
        {
            reader.Read();
            return null;
        }
        else if (reader.TokenType == JsonTokenType.StartObject)
        {
            var clonedReader = reader;
            using var jsonDocument = JsonDocument.ParseValue(ref reader);
            var data = jsonDocument.RootElement;
            var type = data.GetProperty(TypeProperty).GetString() ?? throw new JsonException();
 
            switch (type)
            {
                case nameof(ImageId):
                    return ImageIdConverter.Instance.Read(ref clonedReader, typeof(ImageId), options);
                case nameof(ImageElement):
                    return ImageElementConverter.Instance.Read(ref clonedReader, typeof(ImageElement), options);
                case nameof(ContainerElement):
                    return ContainerElementConverter.Instance.Read(ref clonedReader, typeof(ContainerElementConverter), options);
                case nameof(ClassifiedTextElement):
                    return ClassifiedTextElementConverter.Instance.Read(ref clonedReader, typeof(ClassifiedTextElementConverter), options);
                case nameof(ClassifiedTextRun):
                    return ClassifiedTextRunConverter.Instance.Read(ref clonedReader, typeof(ClassifiedTextRunConverter), options);
                default:
                    return data;
            }
        }
        else if (reader.TokenType == JsonTokenType.String)
        {
            return reader.GetString();
        }
        else if (reader.TokenType == JsonTokenType.Number)
        {
            return reader.GetInt32();
        }
        else
        {
            return JsonSerializer.Deserialize(ref reader, objectType, options);
        }
    }
 
    /// <inheritdoc/>
    public override void Write(Utf8JsonWriter writer, object? value, JsonSerializerOptions options)
    {
        if (value is null)
        {
            writer.WriteNullValue();
            return;
        }
 
        switch (value)
        {
            case ImageId:
                ImageIdConverter.Instance.Write(writer, (ImageId)value, options);
                break;
            case ImageElement:
                ImageElementConverter.Instance.Write(writer, (ImageElement)value, options);
                break;
            case ContainerElement:
                ContainerElementConverter.Instance.Write(writer, (ContainerElement)value, options);
                break;
            case ClassifiedTextElement:
                ClassifiedTextElementConverter.Instance.Write(writer, (ClassifiedTextElement)value, options);
                break;
            case ClassifiedTextRun:
                ClassifiedTextRunConverter.Instance.Write(writer, (ClassifiedTextRun)value, options);
                break;
            default:
                // According to the docs of ContainerElement point to https://docs.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.text.adornments.iviewelementfactoryservice
                // which states that Editor supports ClassifiedTextElement, ContainerElement, ImageElement and that other objects would be presented using ToString unless an extender
                // exports a IViewElementFactory for that type. So I will simply serialize unknown objects as strings.
                writer.WriteStringValue(value.ToString());
                break;
        }
    }
}