Runtime: 支持“动态”和可写 DOM

创建于 2019-05-29  ·  47评论  ·  资料来源: dotnet/runtime

JsonSerializer.Parse(String, Type, JsonSerializerOptions)支持动态ExpandoObject返回类型?

像这样的东西:
dynamic p = JsonSerializer.Parse(json, typeof(ExpandoObject));

area-System.Text.Json enhancement json-functionality-doc

最有用的评论

我也有一个用例 - 我只想使用 HttpClient 调用 REST API 并从响应中检索单个属性。 我不想创建一个专门的类来解析响应......使用 JSON.NET 我可以只使用“动态”并访问选择的属性。

所有47条评论

不,此时不支持此功能,但我们应该为 vNext 考虑。 您是否有示例用法来激发功能请求?

标记为未来。

System.NotSupportedException : The collection type 'System.Dynamic.ExpandoObject' is not supported.

@ahsonkhan GraphQL 就是一个很好的例子。

该规范推荐使用 JSON,但它与响应中的任何特定序列化无关。
这意味着响应的“数据”字段属于以下类型:动态。 因为无法推断。
如果没有 ExpandoObject,反序列化使动态成为 JSON 制作的一种类型。 所以访问抽象的动态“数据”必须知道动态实际上是一个 JToken。

使用 ExpandoObject 我认为我们可以像普通对象一样强制访问动态“数据”

@ahsonkhan另一个例子是我们项目中的配置服务。 它像 REST 端点一样公开集合,这些端点在 MongoDB 中创建集合(它不仅仅是一个虚拟的 REST 包装器,并且休息集合和 mongo 集合没有精确的 1-1 映射,它还断言某些规则)。

所以在我们的项目中我们需要动态/ExpandoObject 支持。

我们也在其他微服务中使用它。

我们也遇到了这个限制。 我们的用例是在json序列化之前逐步构建一个动态对象。 回到更成熟的 Json.NET 序列化器。

大家好

什么是散步呢?
我可以配置使用哪一个吗?
@SidShetye你说了一些关于回到更成熟的事情,你能解释一下吗?

@MickeyReznikov ,您的问题得到解答了吗? 我相信@SidShetye意味着要重新使用Newtonsoft.Json来序列化动态对象,因为内置库 (System.Text.Json) 尚不支持这些动态对象。 对于 asp.net 应用程序,您可以将其配置为 AddNewtonsoftJson。

请参阅https://docs.microsoft.com/en-us/aspnet/core/migration/22-to-30?view=aspnetcore-3.0&tabs=visual-studio#jsonnet -support

我也有一个用例 - 我只想使用 HttpClient 调用 REST API 并从响应中检索单个属性。 我不想创建一个专门的类来解析响应......使用 JSON.NET 我可以只使用“动态”并访问选择的属性。

来自https://github.com/dotnet/corefx/issues/41472 @ghost1372

嗨,我希望这是问这个问题的好地方
似乎我们不能反序列化动态对象
我使用了这段代码但没有用 有什么办法可以做到这一点吗?

var objList = System.Text.Json.JsonSerializer.Deserialize<List<dynamic>>(json);

在许多应用程序中,我们控制来自存储过程的数据字段,并使用 jquery.jtable 动态呈现列表和搜索列表页面

如果没有对 ExpandoObject 的 JsonSerializer 内置支持,我们将无法使用 dotnet 核心内置的 Json 序列化

我们确实共享了@estiller和@SidShetye 已经提到的相同用例
与此同时,我们不得不切换回 Json.NET。

是否有可能比Future更接近 _now_ 的里程碑? 🤔

ExpandoObject 已经在 BCL 中出现了很长时间

ExpandoObject 有什么替代方案吗?

@fatihyildizhan不,没有替代品。

但是我们已经编写了自己的 ExpandoObject 转换器,您可以从这篇文章ASP.NET Core 3.0:用于新 System.Text.Json 的自定义 JsonConverter 中得到提示

我们只需要序列化,所以我们只需要创建序列化

我很惊讶这还不支持。

将其移至 5.0。

迁移到 5.0? 也就是说至少要等一年? 回到 JSON.Net,它是。

5.0? 哇,这绝对糟透了。

对于临时丑陋的解决方法,我可以使用带有自定义转换器的 JsonDocument,但它是 IDisposable。

public sealed class EventObject
    {
        [JsonPropertyName("id")]
        public long Id
        {
            get; set;
        }

        [JsonPropertyName("eventData")]
        [JsonConverter(typeof(JsonDocumentConverter))]
        public System.Text.Json.JsonDocument EventData
        {
            get; set;
        }
    }

    internal sealed class JsonDocumentConverter
        : JsonConverter<System.Text.Json.JsonDocument>
    {
        public override JsonDocument Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            return JsonDocument.ParseValue(ref reader);
        }

        public override void Write(Utf8JsonWriter writer, JsonDocument value, JsonSerializerOptions options)
        {
            value.WriteTo(writer);
        }
    }

在 System.Text.Json 存在的情况下,我们不再能够使用以下 POST 操作:

[HttpPost]
public async Task<IActionResult> SubmitAsync(dynamic model)

相反,我们不得不使用以下方法,但在下游后端代码中没有直接使用“模型”的方法:

[HttpPost]
public async Task<IActionResult> SubmitAsync(JsonElement model)

“模型”是一个复杂对象,它可能包含一组对象和/或其他嵌套的复杂对象。 为了能够从JsonElement得出dynamic对象,我们必须调用 model.GetRawText() 并让 Newtonsoft 将其解码为动态对象。 这种方式不是理想的方式,因为本练习的主要目的是从项目中停用 Newtonsoft.json。

我可以假设解决此票证/问题意味着解决我们遇到的问题吗? 这似乎是一个急需解决的问题,所以它可以早点解决吗?

/cc @ahsonkhan @terrajobst

.NET Core 3.0 JsonSerializer.Deserialize 为动态对象

对 ExpandoObject 的 JsonSerializer 支持(临时措施)
我是新手,很多地方不完善,欢迎大家修改
.net Core3 不支持

添加 Json 转换器

添加使用:

  • 使用 System.Text.Json;
  • 使用 System.Text.Json.Serialization;

```C#
///


/// 温度动态转换器
/// 作者:[email protected]
///

公共类 DynamicJsonConverter : JsonConverter
{
公共覆盖动态读取(参考 Utf8JsonReader 阅读器,
类型类型转换,
JsonSerializerOptions 选项)
{

        if (reader.TokenType == JsonTokenType.True)
        {
            return true;
        }

        if (reader.TokenType == JsonTokenType.False)
        {
            return false;
        }

        if (reader.TokenType == JsonTokenType.Number)
        {
            if (reader.TryGetInt64(out long l))
            {
                return l;
            }

            return reader.GetDouble();
        }

        if (reader.TokenType == JsonTokenType.String)
        {
            if (reader.TryGetDateTime(out DateTime datetime))
            {
                return datetime;
            }

            return reader.GetString();
        }

        if (reader.TokenType == JsonTokenType.StartObject)
        {
            using JsonDocument documentV = JsonDocument.ParseValue(ref reader);
            return ReadObject(documentV.RootElement);
        }
        // Use JsonElement as fallback.
        // Newtonsoft uses JArray or JObject.
        JsonDocument document = JsonDocument.ParseValue(ref reader);
        return document.RootElement.Clone();
    }

    private object ReadObject(JsonElement jsonElement)
    {
        IDictionary<string, object> expandoObject = new ExpandoObject();
        foreach (var obj in jsonElement.EnumerateObject())
        {
            var k = obj.Name;
            var value = ReadValue(obj.Value);
            expandoObject[k] = value;
        }
        return expandoObject;
    }
    private object? ReadValue(JsonElement jsonElement)
    {
        object? result = null;
        switch (jsonElement.ValueKind)
        {
            case JsonValueKind.Object:
                result = ReadObject(jsonElement);
                break;
            case JsonValueKind.Array:
                result = ReadList(jsonElement);
                break;
            case JsonValueKind.String:
                //TODO: Missing Datetime&Bytes Convert
                result = jsonElement.GetString();
                break;
            case JsonValueKind.Number:
                //TODO: more num type
                result = 0;
                if (jsonElement.TryGetInt64(out long l))
                {
                    result = l;
                }
                break;
            case JsonValueKind.True:
                result = true;
                break;
            case JsonValueKind.False:
                result = false;
                break;
            case JsonValueKind.Undefined:
            case JsonValueKind.Null:
                result = null;
                break;
            default:
                throw new ArgumentOutOfRangeException();
        }
        return result;
    }

    private object? ReadList(JsonElement jsonElement)
    {
        IList<object?> list = new List<object?>();
        foreach (var item in jsonElement.EnumerateArray())
        {
            list.Add(ReadValue(item));
        }
        return list.Count == 0 ? null : list;
    }
    public override void Write(Utf8JsonWriter writer,
        object value,
        JsonSerializerOptions options)
    {
       // writer.WriteStringValue(value.ToString());
    }
}
## How to Use?

```C#
var serializerOptions = new JsonSerializerOptions
{
    Converters = { new DynamicJsonConverter() }
};
return JsonSerializer.Deserialize<dynamic>("{OK:"200"}", serializerOptions);

@tchivs ,您的解决方案对我
c# var serializerOptions = new JsonSerializerOptions(); serializerOptions.Converters.Add(new DynamicJsonConverter()); return JsonSerializer.Deserialize<dynamic>("{OK:"200"}", serializerOptions);

尝试使用 JsonElement 类型:

public JsonElement MyProperty {get; set;}

@tchivs ,我对您的代码进行了一些修改 - 对其进行了修改,以便它使用动态生成的“投影”类型(具有来自基类型的属性子集),而不是 ExpandoObject。 我在此处发布了代码(在示例控制台项目中): EDennis.DynamicDeserialization

我将在更复杂的对象和各种场景下测试这种方法(例如,用于修补现有的全类型 EF Core 实体的动态对象的反序列化;测试用例的 json 对象和数组的反序列化)。 如果您觉得这很有用,或者您发现任何问题,请告诉我。

感谢社区解决方法。 令人惊讶的是,Microsoft 无法在合理的时间范围内推出此功能。 在没有某种动态对象的情况下使用 GraphQL 响应会导致大量冗长和丑陋的代码。 或者甚至只是检查是否存在深度嵌套的属性。

我通读了评论线程,大多数都集中在反序列化上,我面临一个问题,即动态对象的序列化显然也“失败”了。 为了重现我的场景,我想出了以下最小重现:

using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Text.Json;

namespace SampleConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            dynamic d = new CustomDynamicObject();
            d.AProperty = 10;
            var s = JsonSerializer.Serialize(d);

            Console.WriteLine(s);
            Console.Read();
        }
    }

    class CustomDynamicObject : DynamicObject 
    {
        private readonly IDictionary<string, object> properties = new Dictionary<string, object>();

        public override bool TryGetMember(GetMemberBinder binder, out object result) => properties.TryGetValue(binder.Name, out result);

        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            properties[binder.Name] = value;
            return true;
        }

        public override IEnumerable<string> GetDynamicMemberNames()
        {
            foreach (var item in properties.Keys)
            {
                yield return item;
            }
        }
    }
}

当运行s{} ,因此序列化不会失败但会产生一个空的 json 对象。

这是正确的问题吗? 或者我应该提高/跟随另一个?

这是正确的问题吗? 或者我应该提高/跟随另一个?

这是正确的问题。 无需为同一特征的两半打开一个。 这个问题是关于添加对 expando 对象的支持(这意味着对双方来说,序列化和反序列化)。

我今天遇到了这个问题 - 想说JsonElement如果它不依赖于已处理的JsonDocument会正常工作。 我能想到的解决这个问题的一种方法是为JsonDocument实现一个析构函数,这样它的处理就可以推迟到以后的时间 - 一旦所有的JsonElement对象都被收集起来。

我还需要一个动态对象。 但是,它还没有实施。 我的解决方案是使用字典。
JsonSerializer.Deserialize<Dictionary<string, string>>(response)
然后查找我需要的密钥:)

只是为了我自己的理智 - 问题是针对 5.0 的特定 ExpandoObject 支持还是反序列化为动态对象的能力? 这里的对话似乎并不完全与问题标题相匹配,我绝对需要后者。 就我而言,我正在反序列化嵌套动态,特别是List<Dictionary<string, dynamic>> 。 现在切换回 Newtonsoft :(

只是想分享一些@tchivs有用代码的 c# 8 语法糖模块 :)

/// <summary>
/// Temp Dynamic Converter with c# 8
/// by:[email protected]
/// </summary>
public class DynamicJsonConverter : JsonConverter<dynamic>
{
    public override dynamic Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        => reader.TokenType switch
        {
            JsonTokenType.True => true,
            JsonTokenType.False => false,
            JsonTokenType.Number => reader.TryGetInt64(out long l) ? 1 : reader.GetDouble(),
            JsonTokenType.String => reader.TryGetDateTime(out DateTime datetime) ? datetime.ToString() : reader.GetString(),
            JsonTokenType.StartObject =>  ReadObject(JsonDocument.ParseValue(ref reader).RootElement),
            // Use JsonElement as fallback.
                _ =>JsonDocument.ParseValue(ref reader).RootElement.Clone()
        };

    private object ReadObject(JsonElement jsonElement)
    {
        IDictionary<string, object> expandoObject = new ExpandoObject();
        foreach (var obj in jsonElement.EnumerateObject())
        {
            var k = obj.Name;
            var value = ReadValue(obj.Value);
            expandoObject[k] = value;
        }
        return expandoObject;
    }
    private object? ReadValue(JsonElement jsonElement)
        =>
         jsonElement.ValueKind switch
        {
            JsonValueKind.Object => ReadObject(jsonElement),
            JsonValueKind.Array => ReadList(jsonElement),
            JsonValueKind.String => jsonElement.GetString(),
            JsonValueKind.Number =>  jsonElement.TryGetInt64(out long l) ? 1 :0,
            JsonValueKind.True => true,
            JsonValueKind.False =>false,
            JsonValueKind.Undefined => null,
            JsonValueKind.Null => null,
                _ => throw new ArgumentOutOfRangeException()
        };

    private object? ReadList(JsonElement jsonElement)
    {
        var list = new List<object?>();
        jsonElement.EnumerateArray().ToList().ForEach(j => list.Add(ReadValue(j)));
        return list.Count == 0 ? null : list;
    }

    public override void Write(Utf8JsonWriter writer,
        object value,
        JsonSerializerOptions options)
        {
        // writer.WriteStringValue(value.ToString());
        }
}

@rs38感谢这里的代码,这正是我所需要的。 想要指出需要的一个非常微妙但重要的变化。 解析“数字”类型的两行在您的压缩版本中不正确:

JsonTokenType.Number => reader.TryGetInt64(out long l) ? 1 : reader.GetDouble(),

应该

JsonTokenType.Number => reader.TryGetInt64(out long l) ? l : reader.GetDouble(),

JsonValueKind.Number => jsonElement.TryGetInt64(out long l) ? 1 :0,

应该

JsonValueKind.Number => jsonElement.TryGetInt64(out long l) ? l :0,

@layomia这并不严重。 早就应该提供支持(事实上,我认为 System.Text.Json 不应该在没有它的情况下启动)! 而且我们甚至没有截止日期!

我的场景是关于 CosmosDB。 我正在使用使用此 JsonSerializer 的新 .NET SDK 进行查询,并且由于它是一个无模式数据库,因此我不想为我需要执行的数据库数据的每个投影创建一个类(有一堆不同的查询) .
我需要查询结果作为动态对象列表。

@SocVi100不要把你的希望寄托在微软@layomia燃起了对近期整合的所有希望。 他们似乎并不真正关心开发人员的需求。 首先,他们大喊“忘记 json.net,我们为您服务!” 幸运的是,开发人员没有忘记 json.net 或类似的东西。

你为什么不贡献? 实施它并提交 PR!

https://github.com/dotnet/runtime/blob/master/docs/coding-guidelines/adding-api-guidelines.md
https://github.com/dotnet/runtime/blob/master/docs/area-owners.md (领导:
@ericstj |,业主: @layomia @steveharter @jozkee)

你为什么不贡献? 实施它并提交 PR!

我希望我能够。

我的场景是关于 CosmosDB。 我正在使用使用此 JsonSerializer 的新 .NET SDK 进行查询,并且由于它是一个无模式数据库,因此我不想为我需要执行的数据库数据的每个投影创建一个类(有一堆不同的查询) .
我需要查询结果作为动态对象列表。

可以使用CosmosClientBuilder.WithCustomSerializer配置不同的序列化

这是一个例子: CosmosJsonNetSerializer

我们无法将此功能放入 5.0。 我们必须优先考虑 5.0 功能的其余部分工作,而这个不适合。 FWIW这非常接近切割线,因此后期移动。

@layomia燃起了对近期整合的所有希望。 他们似乎并不真正关心开发人员的需求。 首先,他们大喊“忘记 json.net,我们为您服务!” 幸运的是,开发人员没有忘记 json.net 或类似的东西。

@RobbyDeLaet我们可以试着保持这个讨论的建设性吗? 我们正在努力在保持设计原则的同时,尽最大努力响应客户要求最高的功能。 关于与 Newtonsoft.Json 的功能对等,这就是我们要说的

System.Text.Json 主要关注性能、安全性和标准合规性。 对于某些场景,System.Text.Json 没有内置功能,但有推荐的解决方法。 如果您的应用程序依赖于缺失的功能,请考虑提交问题以了解是否可以添加对您的场景的支持。

我们的目标不是取代 Newtonsoft.JSON。 如果它对您有用,请继续使用它。 我们将尽最大努力使 System.Text.Json 尽可能有用,同时保持我们在性能、安全性、标准合规性和分层方面取得的成果。 随着时间的推移,我们希望让它为尽可能多的人服务。 我听到了这里的兴趣,并将确保我们专注于向前推进。

感谢您的建议。 我终于走到了中间,只使用 Newtonsoft 序列化器进行反序列化,但我仍然需要测试。 我希望在 CosmosDB 的默认序列化程序上获得 ExpandoObject 支持不会持续太久,这样我就可以摆脱旧的序列化程序。
@RobbyDeLaet ,感谢您的评论! 我对 Microsoft 实施 anyting 没有太多期望,我对它们感到沮丧太多次。 作为一名自由开发人员,我已经等了很多年了,实际上,为了在 CosmosDB、EntityFramework 和 Identity 基础设施上实现基本功能,他们以自己的方式无视我们。 他们在 UserVoice 或其他任何东西上有多少票并不重要。 我认为它自己的开发不需要这些功能,所以超出了范围......
问题总是一样的:太多的营销=太多的期望

为了帮助那些需要此功能的人,请参阅上面的代码示例以帮助解除阻止:

  • 来自@tchivs (对象使用ExpandoObject ,集合使用List<object>
  • 来自@denmitchell (对对象使用 IL Emit 而不是ExpandoObject

为了帮助满足此功能的要求,我将提供一个新的自定义转换器示例,并在准备就绪后将其链接到此处。 之后,我会将该示例添加到Newtonsoft 解决方法部分。 抄送@tdykstra

一个尚未讨论的细节是支持dynamic需要对非常大的System.Linq.Expressions.dll程序集的引用; 这种复杂性可能意味着将来我们在新程序集中添加动态转换器(例如System.Text.Json.Converters.dll ),以便为那些关心部署大小的人(例如独立的应用程序或 Blazor客户端应用程序)。

只是为了我自己的理智 - 问题是针对 5.0 的特定 ExpandoObject 支持还是反序列化为动态对象的能力? 这里的对话似乎并不完全与问题标题相匹配,我绝对需要后者。 就我而言,我正在反序列化嵌套动态,特别是List<Dictionary<string, dynamic>> 。 现在切换回 Newtonsoft :(

我现在正在测试我为反序列化嵌套对象而实现的旧函数,并看到本主题中使用“valueKind”指出的相同问题。

有没有办法使用新的 neetonsoft.json 包来解决这个问题? 因为上面的代码适用于根级对象,但不适用于嵌套对象。

@ericstj抱歉,我的反应可能有点过于消极和苛刻,但就像@SocVi100 一样,我是一名自由开发人员,有时沮丧会占上风。 但至少我们在这里开始了讨论。 感谢您的回复,现在我们至少得到了有关此请求状态的明确消息。

只是为了我自己的理智 - 是针对 5.0 的问题专门针对 ExpandoObject 支持还是反序列化为动态对象的能力

我假设所需的语义是具有动态类型,因此在反序列化后,您可以以后期绑定的方式访问所有属性(包括嵌套):

dynamic obj = JsonSerializer.Deserialize<dynamic>(json);
string s = obj.MyChildProperty.MyStringList[2];

我假设所需的语义也明确支持ExpandoObject

ExpandoObject expando = JsonSerializer.Deserialize<ExpandoObject>(json);
dynamic obj = expando;
string s = obj.MyChildProperty.MyStringList[2];

所以我对范围的理解:

  • Deserialize<dynamic>(json)返回原始值、集合或对象。 此处的对象可能是ExpandoObject但也可能是IDynamicMetaObjectProvider或 JITed 类型,具体取决于实现。 这与今天总是返回JsonElement
  • Deserialize<ExpandoObject>(json) (实现IDynamicMetaObjectProvider )应该创建一个正确的ExpandoObject ,它又可以与dynamic 。 这与今天不同,它只为根属性创建扩展属性,并为所有嵌套属性创建JsonElement实例。
  • Serialize<ExpandoObect>()按预期工作
  • Serialize<IDynamicMetaObjectProvider ()>可能实现也可能不实现,取决于是否存在不使用ExpandoObject场景。

3.0 - 5.0 中的语义:

  • 序列化ExpandoObject有效的,因为ExpandoObject实现了IDictionary<string, object>并且 STJ 将正确序列化每个object值,无论它是原始值、集合还是对象。
  • 反序列化为ExpandoObject类型的作品,但只有根属性是“正确的”扩展属性; 任何嵌套属性都将是JsonElement因此编程模型中存在不一致,因此除非使用自定义转换器,否则应避免反序列化ExpandoObject

3.0-5.0 的选项:

  • 如上面的帖子所述,为ExpandoObject和\或object编写自定义转换器; 我将很快提供一个链接,指向一个理想地映射到 6.0 功能的新示例。
  • 如果你只序列,无法反序列化,你可以使用ExpandoObject (或dynamic如果类型是基于ExpandoObject )。 由于今天在 STJ 中反序列化ExpandoObject存在不一致问题,因此不建议反序列化ExpandoObject
  • 使用JsonElement代替动态对象。 JsonElement不会尝试“猜测”JSON 映射到的类型,因此您必须通过调用 GetString()、GetInt32() 等来明确表示。

此外,根据实现,自定义转换器的实现可能意味着在反序列化期间对 JSON 映射到的 CLR 类型存在一些“猜测”。 例如,JSON 字符串可能映射到DateTimestring ,JSON 数字可能映射到doublelong ,以及需要确定 JSON 数组类型。 这需要与 Newtonsoft 语义进行审查和比较。 这也应该与当属性为object类型时返回的类型一致(今天它是JsonElement )。

以下是 3.0 - 5.0 STJ 语义的一些示例:

using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Diagnostics;
using System.Linq;
using System.Text.Json;

namespace ConsoleApp
{
    class Program
    {
        const string ExpectedJson = "{\"A\":\"A\",\"B\":[1,2],\"C\":42,\"D\":\"2020-01-01T00:00:00\",\"E\":{\"A_Child\":\"A_Child\"}}";

        static void Main(string[] args)
        {
            DateTime dateTime = new DateTime(2020, 1, 1);

            dynamic myDynamicChild = new ExpandoObject();
            myDynamicChild.A_Child = "A_Child";

            dynamic myDynamic = new ExpandoObject();
            myDynamic.A = "A";
            myDynamic.B = new List<int>() { 1, 2 };
            myDynamic.C = 42;
            myDynamic.D = dateTime;
            myDynamic.E = myDynamicChild;

            // Verify we can call late-bound property.
            int c = myDynamic.C;
            Debug.Assert(c == 42);

            // STJ can serialize with ExpandoObject since it implements IDictionary<string, object>.
            string json = JsonSerializer.Serialize<ExpandoObject>(myDynamic);
            Debug.Assert(json == ExpectedJson);

            // Using 'dynamic' against backing ExpandoObject works.
            json = JsonSerializer.Serialize<dynamic>(myDynamic);
            Debug.Assert(json == ExpectedJson);

            // Deserialize with <dynamic>, <object> and <JsonElement>.
            // For 5.0, using one of these is recommended over ExpandoObject because the programming model will be
            // consistent for the root type and all nested types.
            // Using <JsonElement> makes it clear and non-abiguous.
            // Using <object> by default uses 'JsonElement', but can be overridden by a custom converter.
            // Using <dynamic> uses 'object' which uses 'JsonElement'.
            {
                dynamic d = JsonSerializer.Deserialize<dynamic>(json);
                VerifyJsonElement(d);

                try
                {
                    // We will get an exception here if we try to access a dynamic property since 'object' is deserialized
                    // as a JsonElement and not an ExpandoObject.
                    c = d.C;
                    Debug.Fail("Should have thrown Exception!");
                }
                catch (Exception ex)
                {
                    Debug.Assert(ex.Message == "'System.Text.Json.JsonElement' does not contain a definition for 'C'");
                }

                // Serializing with <object> creates a JsonElement by default (can be changed by a custom converter).
                object o = JsonSerializer.Deserialize<object>(json);
                VerifyJsonElement((JsonElement)o);

                // Serialize with explicit <JsonElement>.
                JsonElement e = JsonSerializer.Deserialize<JsonElement>(json);
                VerifyJsonElement(e);
            }

            // Deserialize with ExpandoObject. This creates an ExpandoObject with the root Type having Expando-properties
            // but the value of those properties will be JsonElement. All other nested properties\objects\collections will
            // also be JsonElement. Due to the inconsistency of having only root-level Expando-properties (such as A_Child),
            // deserializing as ExpandoObject is not recommended (unless a ExpandoObject custom converter is used).
            {
                // When STJ deserializes, it creates ExpandoObjects via IDictionary<string, object> where 'object' is JsonElement.
                ExpandoObject expando = JsonSerializer.Deserialize<ExpandoObject>(json);
                Debug.Assert(((IDictionary<string, object>)expando).Keys.Count == 5);
                myDynamic = expando;

                JsonElement jsonElement = myDynamic.A;
                Debug.Assert(jsonElement.GetString() == "A");

                jsonElement = myDynamic.B;
                Debug.Assert(jsonElement.EnumerateArray().Count() == 2);

                jsonElement = myDynamic.C;
                Debug.Assert(jsonElement.GetInt32() == 42);

                jsonElement = myDynamic.D;
                Debug.Assert(jsonElement.GetDateTime() == dateTime);

                jsonElement = myDynamic.E;
                // Here we have an inconsistency. Nested object property must use JsonElement (not a dynamic property).
                Debug.Assert(jsonElement.GetProperty("A_Child").GetString() == "A_Child");

                // Re-serialize works as expected.
                json = JsonSerializer.Serialize<ExpandoObject>(myDynamic);
                Debug.Assert(json == ExpectedJson);

                // Re-serialize works as expected; dynamic works here since backed by ExpandoObject in this example.
                json = JsonSerializer.Serialize<dynamic>(myDynamic);
                Debug.Assert(json == ExpectedJson);
            }

            void VerifyJsonElement(JsonElement elem)
            {
                // Verify JsonElement
                Debug.Assert(elem.GetProperty("A").GetString() == "A");
                Debug.Assert(elem.GetProperty("B").EnumerateArray().Count() == 2);
                Debug.Assert(elem.GetProperty("C").GetInt32() == 42);
                Debug.Assert(elem.GetProperty("D").GetDateTime() == dateTime);
                Debug.Assert(elem.GetProperty("E").GetProperty("A_Child").GetString() == "A_Child");

                // Re-serialize
                json = JsonSerializer.Serialize<dynamic>(elem);
                Debug.Assert(json == ExpectedJson);

                json = JsonSerializer.Serialize<JsonElement>(elem);
                Debug.Assert(json == ExpectedJson);
            }
        }
    }
}

@rs38你能根据 @ryan-hollister-q2 指出的内容修复你的代码片段吗?

正如所承诺的,提供动态实现示例的 PR 位于https://github.com/dotnet/runtime/pull/42097。

@rs38你能根据 @ryan-hollister-q2 指出的内容修复你的代码片段吗?

那不是我的代码,只是在此线程的前面分享了来自@tchivs 的一些片段。

此页面是否有帮助?
0 / 5 - 0 等级