【问题标题】:System.Text.Json and Dynamically Parsing polymorphic objectsSystem.Text.Json 和动态解析多态对象
【发布时间】:2020-03-21 19:24:16
【问题描述】:

我不相信我正在思考如何正确使用 JsonConverter 来解析 json 结果的多态性。

在我的场景中,我的目标是 TFS 中的 Git 策略配置。策略配置:


"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"
        },
{...}]

更多settings例子: 需要合并策略

"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 sn-p中,settings对象根据配置的类型不同。

除了动态序列化/反序列化设置对象之外,编写转换器的最佳方法是什么?我已经阅读了几篇关于此的文章,但无法完全理解它。


这就是我目前反序列化所有 API 结果的方式,到目前为止,它们都是简单的结果集。

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 是我要反序列化的类型。

[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 );
}

我目前针对这种解析情况的课程

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; }
}

【问题讨论】:

  • settings 是您的示例中的单个令牌,为什么要序列化为 List&lt;T&gt;?您是否在解析 scope 值?你通过了哪个parameter,哪些typeparams被用作T
  • @PavelAnikhouski 这是我序列化整个 json 结果的方法。在这种情况下,结果是计数 4,父对象是 value 类型的数组。 settings 是值的子对象,并且是动态的。如果有帮助,我可以发布我的对象类型吗?本质上这是一个PolicyConfiguration,属性是PolicyConfigurationSettings
  • @PavelAnikhouski 我已经用用于反序列化的类更新了我的问题。设置被注释掉并且一切正常,当我为列出的设置插入类时,它会尝试将设置解析为每个配置的设置,从这里我不确定将转换器放在哪里。
  • --编辑:我相信我现在拥有原始问题中的所有内容来解决我当前的设置。
  • 谢谢,现在问题更清楚了。您是否需要一个确切的类型进行反序列化,您是否考虑过使用键和值字典?

标签: c# .net-core-3.1 system.text.json


【解决方案1】:

我最终解决了我的问题,方式与我在之前的文章中看到的使用鉴别器的方式相同。由于我不控制 API 提要,因此我没有可驱动的鉴别器,因此我依赖于 Json 对象的属性。

需要创建一个转换器:

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

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

将选项传递到您的 Serializer/Deserializer 语句中。

下面是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 { }

【讨论】:

  • JsonDocument 实现 IDisposable 并且实际上必须被释放,因为 此类利用池内存中的资源来最小化垃圾收集器 (GC) 在高使用情况下的影响。根据docs,未能正确处置此对象将导致内存无法返回到池中,这将增加跨框架各个部分的 GC 影响
  • 此外,将JsonDocument 重新序列化为字节数组而不是最终反序列化的字符串可能会获得更好的性能。见System.Text.Json.JsonElement ToObject workaround
  • 谢谢@dbc,我将对我的代码进行这些更改并更新此答案!
【解决方案2】:

在带有System.Text.Json.JsonSerializer 的 net 5.0 中,这样的类适用于:

public class A
{
    public B Data { get; set; }
}
public class B
{
    public long Count { get; set; }
}

正在使用:

System.Text.Json.JsonSerializer.Deserialize<A>("{{\"data\":{\"count\":10}}}", new JsonSerializerOptions { PropertyNameCaseInsensitive = true, IncludeFields = true })

这很奇怪,这不是默认值。

【讨论】:

    【解决方案3】:

    我用一种更通用的方法解决了这个问题,它介于 NewtonSoft Json 和 .NET Json 的工作方式之间。 使用自定义转换器,我序列化任何多态类,使用类似于 Newtonsoft 方法的类型标识符,但为了减轻可能的安全风险,您可以选择仅允许内部类型或来自特定程序集的类型。

    using System.Text.Json;
    using System.Text.Json.Serialization;
    using System.Diagnostics.CodeAnalysis;
    using System.Diagnostics;
    using System.Collections.ObjectModel;
    
    public class JsonConverterEx<T> : System.Text.Json.Serialization.JsonConverter<T>
    {
        private bool _internalOnly = true;
        private string _assembly = String.Empty;
    
        public JsonConverterEx()
        {
            this._assembly = this.GetType().Assembly.FullName;
        }
    
        public JsonConverterEx(bool bInternalOnly, string assemblyName)
        {
            _internalOnly = bInternalOnly;
            _assembly = assemblyName;
        }
    
        public override bool CanConvert(Type typeToConvert)
        {
            Type t = typeof(T);
    
            if(typeToConvert == t)
                return true;
    
            return false;
        }
    
        public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            validateToken(reader, JsonTokenType.StartObject);
    
            reader.Read();      // Move to property name
            validateToken(reader, JsonTokenType.PropertyName);
    
            var typeKey = reader.GetString();
    
            reader.Read();      // Move to start of object (stored in this property)
            validateToken(reader, JsonTokenType.StartObject);
    
            if(!_internalOnly)
            {
                typeKey += ", " + _assembly;
            }
    
            Type t = Type.GetType(typeKey);
            if(t != null)
            {
                T o = (T)JsonSerializer.Deserialize(ref reader, t, options);
                reader.Read(); // Move past end of item object
    
                return o;
            }
            else
            {
                throw new JsonException($"Unknown type '{typeKey}'");
            }
    
            // Helper function for validating where you are in the JSON
            void validateToken(Utf8JsonReader reader, JsonTokenType tokenType)
            {
                if(reader.TokenType != tokenType)
                    throw new JsonException($"Invalid token: Was expecting a '{tokenType}' token but received a '{reader.TokenType}' token");
            }
        }
    
        public override void Write(Utf8JsonWriter writer, [DisallowNull] T value, JsonSerializerOptions options)
        {
            var itemType = value.GetType();
    
            writer.WriteStartObject();
            writer.WritePropertyName(itemType.FullName);
    
            // pass on to default serializer
            JsonSerializer.Serialize(writer, value, itemType, options);
    
            writer.WriteEndObject();
        }
    }
    

    使用方法:

            JsonSerializerOptions op = new JsonSerializerOptions()
            {
                // your usual options here
            };
            op.Converters.Add(new JsonConverterEx<MyExternalClass>(false, "MyAssembly"));
            op.Converters.Add(new JsonConverterEx<MyInternalClass>());
    
            string s = System.Text.Json.JsonSerializer.Serialize(myobj, op);
    
            MyInternalClass c = System.Text.Json.JsonSerializer.Deserialize<MyInternalClass>(s, op);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-08-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多