System.Text.Json和动态解析多态对象
我不相信我会全神贯注于如何正确地使用JsonConverter来解析json结果中的多态性。
I don't believe I am wrapping my head around how to properly use JsonConverter for polymorphism in parsing json results.
在我的场景中,我的目标是TFS中的Git策略配置。一个策略配置:
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"
}
]
}
必需的审阅者
"settings": {
"requiredReviewerIds": [
"id"
],
"scope": [
{
"refName": "refs/heads/master",
"matchKind": "Exact",
"repositoryId": "id"
}
]
}
在上面的json片段中,设置对象根据配置类型而有所不同。
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.
这就是方法我目前正在反序列化我所有的API结果,到目前为止,它们都是简单的结果集。
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;
}
我的集成测试。
PolicyConfiguration
是我要反序列化的类型。
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; }
}
我最终解决了我的问题问题与我以前看过使用鉴别器的文章几乎相同。由于我不控制API提要,因此没有可区分的标识符,因此我依赖于Json对象的属性。
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.
需要创建转换器:
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 );
}
}
然后需要创建 JsonSerializerOptions
对象以添加Converter
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;
}
将选项传递到Serializer / Deserializer语句中。
Pass the options into your Serializer/Deserializer statement.
下面是 PolicyConfigurationSettings
类
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 { }