c# - System.Text.Json 中的动态类型处理/"object"属性的自定义反序列化

标签 c# blazor json-deserialization system.text.json

对于 Blazor 控件中的某些动态数据绑定(bind),我必须使用如下模型:

public class DynamicDataGridResult
{
    public DynamicDataGridResult()
    {
        Columns = new Dictionary<string, ColumnDefinition>();
        Data = new List<Dictionary<string, object>>();
    }

    public Dictionary<string, ColumnDefinition> Columns { get; set; }
    
    public List<Dictionary<string, object>> Data { get; set; }   
}

public class ColumnDefinition
{
    public string PropertyName { get; set; }
    public string TypeFullName { get; set; }
    public string Title { get; set; }
    public string? Formatter { get; set; }

    public Type GetColumnType()
    {
        var type = Type.GetType(TypeFullName);
        return type;
    }
}

在“列”中,我描述了我的列(用于直接以 .Net 方式显示的 TypeFullName、用于微调 ToString(xx) 的格式化程序等)。

在数据中,我可以输入我的值。

这是两者的工作示例(下面的代码仅用于示例目的,以示例数据进行说明,但对我的问题并不重要,如果您想跳过,请参阅下文):

        DynamicDataGridResult dataGridModel = new();

    var columns = new List<ColumnDefinition>()
    {
        new() { TypeFullName = typeof(int).FullName!, PropertyName = "EmployeeID", Title = "Employee ID"},
        new() { TypeFullName = typeof(decimal?).FullName!, PropertyName = "Rating", Title = "Rating (Nullable<decimal>)" },
        new() { TypeFullName = typeof(string).FullName!, PropertyName = "FirstName", Title = "First name"},
        new() { TypeFullName = typeof(string).FullName!, PropertyName = "LastName", Title = "Last name"},
        new() { TypeFullName = typeof(DateTime).FullName!, PropertyName ="HireDate", Title = "Hire date"},
    };

    columns.ForEach(c =>
    {
        if (c.TypeFullName.Contains("Decimal", StringComparison.OrdinalIgnoreCase)
            || c.TypeFullName.Contains("Double", StringComparison.OrdinalIgnoreCase)
            || c.TypeFullName.Contains("Single", StringComparison.OrdinalIgnoreCase))
        {
            c.Formatter = "{0:N2}";
        }
    });

    dataGridModel.Columns = columns.ToDictionary(k => k.PropertyName);

    dataGridModel.Data = Enumerable.Range(0, 25).Select(i =>
    {
        var row = new Dictionary<string, object>();

        foreach (var column in dataGridModel.Columns)
        {
            object value;
            if (column.Value.TypeFullName == typeof(int).FullName)
                value = i;
            else if (column.Value.TypeFullName == typeof(decimal?).FullName)
                value = i % 3 != 0 ? ((double)i / 2.1234) : null;
            else if (column.Value.TypeFullName == typeof(DateTime).FullName)
                value = DateTime.Now.AddMonths(i);
            else
                value = $"{column.Key}{i}";

            row.Add(column.Key, value);
        }

        return row;
    }).ToList();

我相信你明白了。

如果我直接将控件与这些对象绑定(bind),就可以了。

但是,如果我通过 System.Text.Json 的 Http.PostAsJsonAsync(url)/ReadFromJsonAsync() 序列化/反序列化它们,它就会被破坏,因为我所有的“对象”都变成了一些“JsonElement”。 我当然理解这种行为,现在我必须处理它。

所以我的问题是:如果我在另一个属性中有类型名称,如何将它们反序列化为常规 .Net 对象?

我搜索创建一个自定义 JsonConverter,但这是一个巨大的野兽,有很多子类和内部/密封类,所以我能够覆盖它。

或者,我在反序列化后做了其他一些事情来“重新计算”我的字典。

下面是转换方法的一部分:

private IEnumerable<Dictionary<string, object>> JsonElementAsObjects()
{
    var liste = new List<Dictionary<string, object>>();
    foreach (var row in GridResult.Data)
    {
        var dictionary = new Dictionary<string, object>();

        foreach (var col in row)
        {
            var columnDefinition = Columns[col.Key];
            
            object value = null;

            if (col.Value != null)
            {
                value = GetObject((JsonElement) col.Value, columnDefinition);
            }
            dictionary.Add(col.Key, value);
        }

        liste.Add(dictionary);
    }

    return liste;
}

我试图在 JsonElement/JsonValue 中找到一个方法将其转换为对象,但我只找到了一个通用方法( ConvertJsonElement<TypeToConvert>() 中的 JsonValue<T> 这并没有真正帮助,因为我的类型仅在运行时已知) .

所以我临时提取了其中的一些内容,并将其调整为使用运行时类型:

private object GetObject(JsonElement element, ColumnDefinition columnDefinition)
{
    var typeToConvert = columnDefinition.GetColumnType();

    switch (element.ValueKind)
    {
        case JsonValueKind.Number:
            if (typeToConvert == typeof(int) || typeToConvert == typeof(int?))
            {
                return element.GetInt32();
            }

            if (typeToConvert == typeof(long) || typeToConvert == typeof(long?))
            {
                return element.GetInt64();
            }

            if (typeToConvert == typeof(double) || typeToConvert == typeof(double?))
            {
                return element.GetDouble();
            }

            if (typeToConvert == typeof(short) || typeToConvert == typeof(short?))
            {
                return element.GetInt16();
            }

            if (typeToConvert == typeof(decimal) || typeToConvert == typeof(decimal?))
            {
                return element.GetDecimal();
            }

            if (typeToConvert == typeof(byte) || typeToConvert == typeof(byte?))
            {
                return element.GetByte();
            }

            if (typeToConvert == typeof(float) || typeToConvert == typeof(float?))
            {
                return element.GetSingle();
            }

            if (typeToConvert == typeof(uint) || typeToConvert == typeof(uint?))
            {
                return element.GetUInt32();
            }

            if (typeToConvert == typeof(ushort) || typeToConvert == typeof(ushort?))
            {
                return element.GetUInt16();
            }

            if (typeToConvert == typeof(ulong) || typeToConvert == typeof(ulong?))
            {
                return element.GetUInt64();
            }

            if (typeToConvert == typeof(sbyte) || typeToConvert == typeof(sbyte?))
            {
                return element.GetSByte();
            }
            break;

        case JsonValueKind.String:
            if (typeToConvert == typeof(string))
            {
                return element.GetString()!;
            }

            if (typeToConvert == typeof(DateTime) || typeToConvert == typeof(DateTime?))
            {
                return element.GetDateTime();
            }

            if (typeToConvert == typeof(DateTimeOffset) || typeToConvert == typeof(DateTimeOffset?))
            {
                return element.GetDateTimeOffset();
            }

            if (typeToConvert == typeof(Guid) || typeToConvert == typeof(Guid?))
            {
                return element.GetGuid();
            }

            if (typeToConvert == typeof(char) || typeToConvert == typeof(char?))
            {
                string? str = element.GetString();
                Debug.Assert(str != null);
                if (str.Length == 1)
                {
                    return str[0];
                }
            }
            break;

        case JsonValueKind.True:
        case JsonValueKind.False:
            if (typeToConvert == typeof(bool) || typeToConvert == typeof(bool?))
            {
                return element.GetBoolean();
            }
            break;
    }

    return element;
}

它与此完美配合。

但是这显然是一个繁重无用的后期处理,需要维护大量样板代码。

您能否给我一个更清晰的解决方案,将我的数据反序列化为“真实对象”,或者至少像我一样在反序列化后调整它们,但使用更短的代码,使用 System.Text.Json 中的一些隐藏技巧?

谢谢!

最佳答案

快速而肮脏的方法是将 UnknownTypeHandling 设置为 JsonUnknownTypeHandling.JsonNode 进行临时反序列化(就像您所做的那样),然后使用非通用 JsonNode .反序列化。类似于以下内容:

var res = JsonSerializer.Deserialize<DynamicDataGridResult>(jsonString, new JsonSerializerOptions
{
    UnknownTypeHandling = JsonUnknownTypeHandling.JsonNode
});

res = new DynamicDataGridResult
{
    Columns = res.Columns,
    Data = res.Data
        .Select(d => d
            .ToDictionary(
                d => d.Key,
                d =>(d.Value as JsonNode)?.Deserialize(res.Columns[d.Key].GetColumnType())))
        .ToList()
};

或者使用 JsonElement 进行相同的操作:

var res = JsonSerializer.Deserialize<DynamicDataGridResult>(jsonString);
res = new DynamicDataGridResult
{
    Columns = res.Columns,
    Data = res.Data
        .Select(d => d
            .ToDictionary(
                d => d.Key,
                d =>(d.Value as JsonElement?)?.Deserialize(res.Columns[d.Key].GetColumnType())))
        .ToList()
};

关于c# - System.Text.Json 中的动态类型处理/"object"属性的自定义反序列化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/76765471/

相关文章:

java - 使用 Jackson 反序列化包含在具有未知属性名称的对象中的 JSON

c# - 为什么这个匿名类型不能使用 JsonConvert.DeserializeAnonymousType 正确反序列化?

c# - 在 Silverlight 中同步填充自定义控件的 DependencyProperties

c# - Blazor、MatBlazor - 如何捕捉 MatSelect 组件的值变化

c# - 如何让 System.Diagnostics 在 Ubuntu Linux 上的 Blazor 应用程序中工作?

asp.net-core - Blazor:如何从子组件中的事件中获取发送者

xml-serialization - 将 ArrayList 中的对象序列化为 XML 时丢失类型元素

c# - Newtonsoft Json 反序列化为 C# Datagridview

c# - 微软是否更改了随机默认种子?

c# - 使用字段填写 PDF