File: Serialization\DiscoveryCriteriaConverter.cs
Web Access
Project: src\src\vstest\src\Microsoft.TestPlatform.CommunicationUtilities\Microsoft.TestPlatform.CommunicationUtilities.csproj (Microsoft.TestPlatform.CommunicationUtilities)
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

#if NETCOREAPP

using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;

using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client;

namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Serialization;

/// <summary>
/// JSON converter for <see cref="DiscoveryCriteria"/> that skips the computed <c>Sources</c> property
/// (marked with <c>[IgnoreDataMember]</c>) during serialization and populates <c>AdapterSourceMap</c>
/// and other private-setter properties via reflection during deserialization.
/// </summary>
internal class DiscoveryCriteriaConverter : JsonConverter<DiscoveryCriteria>
{
    /// <inheritdoc/>
    public override DiscoveryCriteria? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        if (reader.TokenType == JsonTokenType.Null)
        {
            return null;
        }

        using var doc = JsonDocument.ParseValue(ref reader);
        var root = doc.RootElement;

        var adapterSourceMap = DeserializeProperty<Dictionary<string, IEnumerable<string>>>(root, "AdapterSourceMap", options);
        var frequency = DeserializeProperty<long>(root, "FrequencyOfDiscoveredTestsEvent", options);
        var timeout = DeserializeProperty<TimeSpan>(root, "DiscoveredTestEventTimeout", options);
        var runSettings = root.TryGetProperty("RunSettings", out var rs) && rs.ValueKind != JsonValueKind.Null ? rs.GetString() : null;
        var package = root.TryGetProperty("Package", out var pkg) && pkg.ValueKind != JsonValueKind.Null ? pkg.GetString() : null;
        var testCaseFilter = root.TryGetProperty("TestCaseFilter", out var tcf) && tcf.ValueKind != JsonValueKind.Null ? tcf.GetString() : null;
        var testSessionInfo = DeserializeProperty<TestSessionInfo>(root, "TestSessionInfo", options);

        var criteria = new DiscoveryCriteria();

        // Set private-setter properties via reflection.
        var type = typeof(DiscoveryCriteria);
        type.GetProperty(nameof(DiscoveryCriteria.AdapterSourceMap))!.SetValue(criteria, adapterSourceMap);
        type.GetProperty(nameof(DiscoveryCriteria.FrequencyOfDiscoveredTestsEvent))!.SetValue(criteria, frequency);
        type.GetProperty(nameof(DiscoveryCriteria.DiscoveredTestEventTimeout))!.SetValue(criteria, timeout);
        type.GetProperty(nameof(DiscoveryCriteria.RunSettings))!.SetValue(criteria, runSettings);

        criteria.Package = package;
        criteria.TestCaseFilter = testCaseFilter;
        criteria.TestSessionInfo = testSessionInfo;

        return criteria;
    }

    /// <inheritdoc/>
    public override void Write(Utf8JsonWriter writer, DiscoveryCriteria value, JsonSerializerOptions options)
    {
        writer.WriteStartObject();

        // Serialize [DataMember] properties in declaration order — skip Sources which has [IgnoreDataMember].
        WriteProperty(writer, "Package", value.Package, options);
        WriteProperty(writer, "AdapterSourceMap", value.AdapterSourceMap, options);
        writer.WriteNumber("FrequencyOfDiscoveredTestsEvent", value.FrequencyOfDiscoveredTestsEvent);
        WriteProperty(writer, "DiscoveredTestEventTimeout", value.DiscoveredTestEventTimeout, options);
        WriteProperty(writer, "RunSettings", value.RunSettings, options);
        WriteProperty(writer, "TestCaseFilter", value.TestCaseFilter, options);
        WriteProperty(writer, "TestSessionInfo", value.TestSessionInfo, options);

        writer.WriteEndObject();
    }

    private static T? DeserializeProperty<T>(JsonElement element, string name, JsonSerializerOptions options)
    {
        if (element.TryGetProperty(name, out var prop) && prop.ValueKind != JsonValueKind.Null)
        {
            return JsonSerializer.Deserialize<T>(prop.GetRawText(), options);
        }

        return default;
    }

    private static void WriteProperty<T>(Utf8JsonWriter writer, string name, T value, JsonSerializerOptions options)
    {
        writer.WritePropertyName(name);
        JsonSerializer.Serialize(writer, value, options);
    }
}

#endif