问题
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