System.Text.Json and Dynamically Parsing polymorphic objects

这一生的挚爱 提交于 2020-07-28 04:22:51

问题


I don't believe I am wrapping my head around how to properly use JsonConverter for polymorphism in parsing json results.

In my scenario, I am targeting Git Policy Configurations in TFS. A policy configuration:


"value": [
{
        "createdBy": {
            "displayName": "username",
            "url": "url",
            "id": "id",
            "uniqueName": "user",
            "imageUrl": "url"
        },
        "createdDate": "2020-03-21T18:17:24.3240783Z",
        "isEnabled": true,
        "isBlocking": true,
        "isDeleted": false,
        "settings": {
            "minimumApproverCount": 1,
            "creatorVoteCounts": false,
            "allowDownvotes": false,
            "resetOnSourcePush": true,
            "scope": [{
                    "refName": "refs/heads/master",
                    "matchKind": "Exact",
                    "repositoryId": "id"
                }
            ]
        },
        "_links": {
            "self": {
                "href": "url"
            },
            "policyType": {
                "href": "url"
            }
        },
        "revision": 1,
        "id": 974,
        "url": "url",
        "type": {
            "id": "id",
            "url": "url",
            "displayName": "Minimum number of reviewers"
        },
{...}]

More settings examples: Require a Merge Strategy

"settings": {
        "useSquashMerge": true,
        "scope": [
            {
                "refName": "refs/heads/master",
                "matchKind": "Exact",
                "repositoryId": "id"
            }
        ]
    }

Required Reviewers

    "settings": {
        "requiredReviewerIds": [
            "id"
        ],
        "scope": [
            {
                "refName": "refs/heads/master",
                "matchKind": "Exact",
                "repositoryId": "id"
            }
        ]
    }

In the json snippet above, the settings object is different based on the type of configuration.

What is the best approach to writing a converter than can dynamically serialize/deserialize the settings object? I've read a couple of articles regarding this and can't quite wrap my head around it.


This is how I am currently deserializing all of my API results, so far they have been simple result sets.

async Task<List<T>> ParseResults<T>( HttpResponseMessage result, string parameter )
{
    List<T> results = new List<T>();

    if ( result.IsSuccessStatusCode )
    {
        using var stream = await result.Content.ReadAsStreamAsync();
        JsonDocument doc = JsonDocument.Parse( stream );
        JsonElement collection = doc.RootElement.GetProperty( parameter ).Clone();

        foreach ( var item in collection.EnumerateArray() )
        {
            results.Add( JsonSerializer.Deserialize<T>( item.ToString() ) );
        }
    }

    return results;
}

My integration test.

PolicyConfiguration is the type I am trying to deserialize to.

[Test]
public async Task Get_TestMasterBranchPolicyConfigurations()
{
    HttpResponseMessage result = await GetResult( $"{_collection}/ProductionBuildTesting/_apis/policy/configurations?api-version=4.1" );

    List<PolicyConfiguration> configurations = await ParseResults<PolicyConfiguration>( result, "value" );
    Assert.AreEqual( 16, configurations.Count );
    JsonPrint( configurations );
}

My current classes for this parsing situation

public class CreatedBy
{
    [JsonPropertyName( "displayName" )]
    public string DisplayName { get; set; }
    [JsonPropertyName( "url" )]
    public string Url { get; set; }
    [JsonPropertyName( "id" )]
    public Guid Id { get; set; }
    [JsonPropertyName( "uniqueName" )]
    public string UniqueName { get; set; }
    [JsonPropertyName( "imageUrl" )]
    public string ImageUrl { get; set; }
}

public class PolicyConfigurationScope
{
    [JsonPropertyName( "refName" )]
    public string RefName { get; set; }
    [JsonPropertyName( "matchKind" )]
    public string MatchKind { get; set; }
    [JsonPropertyName( "repositoryId" )]
    public Guid RepositoryId { get; set; }
}

public class PolicyConfigurationSettings_MinimumNumberOfReviewers
{
    [JsonPropertyName( "minimumApproverCount" )]
    public int MinimumApproverCount { get; set; }
    [JsonPropertyName( "creatorVoteCounts" )]
    public bool CreatorVoteCounts { get; set; }
    [JsonPropertyName( "allowDownvotes" )]
    public bool AllowDownvotes { get; set; }
    [JsonPropertyName( "resetOnSourcePush" )]
    public bool ResetOnSourcePush { get; set; }
    [JsonPropertyName( "scope" )]
    public List<PolicyConfigurationScope> Scope { get; set; }
}

public class PolicyConfigurationType
{
    [JsonPropertyName( "id" )]
    public Guid Id { get; set; }
    [JsonPropertyName( "url" )]
    public string Url { get; set; }
    [JsonPropertyName( "displayName" )]
    public string DisplayName { get; set; }
}

public class PolicyConfiguration
{
    [JsonPropertyName( "createdBy" )]
    public CreatedBy CreatedBy { get; set; }
    [JsonPropertyName( "createdDate" )]
    public DateTime CreatedDate { get; set; }
    [JsonPropertyName( "isEnabled" )]
    public bool IsEnabled { get; set; }
    [JsonPropertyName( "isBlocking" )]
    public bool IsBlocking { get; set; }
    [JsonPropertyName( "isDeleted" )]
    public bool IsDeleted { get; set; }
    //[JsonPropertyName( "settings" )]
    //public PolicyConfigurationSettings_MinimumNumberOfReviewersSettings Settings { get; set; }
    [JsonPropertyName( "revision" )]
    public int Revision { get; set; }
    [JsonPropertyName( "id" )]
    public int Id { get; set; }
    [JsonPropertyName( "url" )]
    public string Url { get; set; }
    [JsonPropertyName( "type" )]
    public PolicyConfigurationType Type { get; set; }
}

回答1:


I ended up solving my issue in slightly the same way I had seen a previous article using a discriminator. Since I do not control the API feeds, I do not have a discriminator to drive off of, so I am relying on the properties of the Json object.

Need to create a Converter:

public class PolicyConfigurationSettingsConverter : JsonConverter<PolicyConfigurationSettings>
{
    public override PolicyConfigurationSettings Read( ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options )
    {
        JsonDocument doc;
        JsonDocument.TryParseValue( ref reader, out doc );

        if ( doc.RootElement.TryGetProperty( "minimumApproverCount", out _ ) )
            return JsonSerializer.Deserialize<MinimumNumberOfReviewers>( doc.RootElement.ToString(), options );
        if ( doc.RootElement.TryGetProperty( "useSquashMerge", out _ ) )
            return JsonSerializer.Deserialize<RequireAMergeStrategy>( doc.RootElement.ToString(), options );
        if ( doc.RootElement.TryGetProperty( "scope", out _ ) )
            return JsonSerializer.Deserialize<PolicyConfigurationSettingsScope>( doc.RootElement.ToString(), options );

        return null;
    }

    public override void Write( Utf8JsonWriter writer, [DisallowNull] PolicyConfigurationSettings value, JsonSerializerOptions options )
    {
        if ( value.GetType() == typeof( MinimumNumberOfReviewers ) )
            JsonSerializer.Serialize( writer, ( MinimumNumberOfReviewers )value, options );
        if ( value.GetType() == typeof( RequireAMergeStrategy ) )
            JsonSerializer.Serialize( writer, ( RequireAMergeStrategy )value, options );
        if ( value.GetType() == typeof( PolicyConfigurationSettingsScope ) )
            JsonSerializer.Serialize( writer, ( PolicyConfigurationSettingsScope )value, options );
    }
}

Then need to create a JsonSerializerOptions object to add the Converter

public static JsonSerializerOptions PolicyConfigurationSettingsSerializerOptions()
{
    var serializeOptions = new JsonSerializerOptions();
    serializeOptions.Converters.Add( new PolicyConfigurationSettingsConverter() );
    return serializeOptions;
}

Pass the options into your Serializer/Deserializer statement.

Below is the PolicyConfigurationSettings class

public abstract class PolicyConfigurationSettings
{
    [JsonPropertyName( "scope" )]
    public List<PolicyConfigurationScope> Scope { get; set; }
}

public class MinimumNumberOfReviewers : PolicyConfigurationSettings
{
    [JsonPropertyName( "minimumApproverCount" )]
    public int MinimumApproverCount { get; set; }
    [JsonPropertyName( "creatorVoteCounts" )]
    public bool CreatorVoteCounts { get; set; }
    [JsonPropertyName( "allowDownvotes" )]
    public bool AllowDownvotes { get; set; }
    [JsonPropertyName( "resetOnSourcePush" )]
    public bool ResetOnSourcePush { get; set; }
}

public class RequireAMergeStrategy : PolicyConfigurationSettings
{
    [JsonPropertyName( "useSquashMerge" )]
    public bool UseSquashMerge { get; set; }
}

public class PolicyConfigurationSettingsScope : PolicyConfigurationSettings { }


来源:https://stackoverflow.com/questions/60792311/system-text-json-and-dynamically-parsing-polymorphic-objects

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!