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; }
}
Text. Json doesn't support the serialization of polymorphic type hierarchies. For example, if a property's type is an interface or an abstract class, only the properties defined on the interface or abstract class are serialized, even if the runtime type has additional properties.
You can use this schema when defining XML Type hierarchies by using only the base XML Types. The XML schema defines XML Types that inherit from each other. In the JSON, an object carries no additional information about the type.
Text. Json. Serialization namespace, which contains attributes and APIs for advanced scenarios and customization specific to serialization and deserialization.
Deserializes the JSON to the specified . NET type. Deserializes the JSON to the specified . NET type using a collection of JsonConverter.
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 { }
In net 5.0 with System.Text.Json.JsonSerializer
, what works for a class like this:
public class A
{
public B Data { get; set; }
}
public class B
{
public long Count { get; set; }
}
is using:
System.Text.Json.JsonSerializer.Deserialize<A>("{{\"data\":{\"count\":10}}}", new JsonSerializerOptions { PropertyNameCaseInsensitive = true, IncludeFields = true })
which is weird that is not the default.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With