我可以将匿名类型序列化为 xml 吗?

Posted

技术标签:

【中文标题】我可以将匿名类型序列化为 xml 吗?【英文标题】:Can I serialize Anonymous Types as xml? 【发布时间】:2011-01-25 04:05:01 【问题描述】:

我知道匿名类型被编译器标记为私有,并且属性是只读的。有没有办法将它们序列化为 xml(不反序列化)?它works with JSON,我该如何使用 XML 呢?

【问题讨论】:

【参考方案1】:

这样的事情应该让你开始......

class Program

    static void Main(string[] args)
    
        var me = new
        
            Hello = "World",
            Other = new
            
                My = "Object",
                V = 1,
                B = (byte)2
            
        ;

        var x = me.ToXml();
    

public static class Tools

    private static readonly Type[] WriteTypes = new[] 
        typeof(string), typeof(DateTime), typeof(Enum), 
        typeof(decimal), typeof(Guid),
    ;
    public static bool IsSimpleType(this Type type)
    
        return type.IsPrimitive || WriteTypes.Contains(type);
    
    public static XElement ToXml(this object input)
    
        return input.ToXml(null);
    
    public static XElement ToXml(this object input, string element)
    
        if (input == null)
            return null;

        if (string.IsNullOrEmpty(element))
            element = "object";
        element = XmlConvert.EncodeName(element);
        var ret = new XElement(element);

        if (input != null)
        
            var type = input.GetType();
            var props = type.GetProperties();

            var elements = from prop in props
                           let name = XmlConvert.EncodeName(prop.Name)
                           let val = prop.GetValue(input, null)
                           let value = prop.PropertyType.IsSimpleType()
                                ? new XElement(name, val)
                                : val.ToXml(name)
                           where value != null
                           select value;

            ret.Add(elements);
        

        return ret;
    

...生成的 xml ...

<object>
  <Hello>World</Hello>
  <Other>
    <My>Object</My>
    <V>1</V>
    <B>2</B>
  </Other>
</object>

【讨论】:

我猜Type.IsValueType 对于大多数IsAssignableFrom 来说可能是一个不错的捷径。但没有捕捉到string IsValueType 可能是一个错误的选择。这将使用 ToString 值来转换类型。但我做了一个应该更容易理解的更改。 嘿 - 你的代码就像一个魅力。我做了一些小改动,使其支持数组,我在这里写过:martinnormark.com/… 好的,这是一个很好的示例......现在,我如何序列化对象,包含匿名类型......我想通过自定义序列化......跨度> 这可能对很多人没有用,但它是这个特定问题的正确答案【参考方案2】:

使用XmlSerializerDataContractSerializer 都无法完成。它可以通过手动编写的代码来完成,如 below 所示(我无法评论代码是否足够全面以处理所有类型 - 但这是一个非常好的开始)。

【讨论】:

但这并不意味着您不能使用第三方课程。 可以写一些通用的东西吗? @Radu:我不知道你的意思是“写一些通用的东西”。如果您正在谈论使用 XML 序列化程序,则不会。 “Matthew Whited”的回答向您展示了如何在不使用 XML 序列化器的情况下做到这一点。 这可能是拒绝所有未标记为“正确”答案的答案的人之一。遗憾的是人们看不到您也是正确的,因为内置的 xml 序列化程序不起作用。 我相信以下 Matthews 的回答是正确的答案。我拿走了他的代码,它开箱即用 - 我做了一些更改以使其支持数组:martinnormark.com/…【参考方案3】:

谢谢@Matthew 和@Martin 的出色工作。

我做了一些修改以适应 Nullables 和 Enums。另外我已经更改了它,以便根据属性的名称+索引来命名数组元素。

如果有人感兴趣,这里是代码

public static class ObjectExtensions 
    #region Private Fields
    private static readonly Type[] WriteTypes = new[] 
        typeof(string), typeof(DateTime), typeof(Enum), 
        typeof(decimal), typeof(Guid),
    ;
    #endregion Private Fields
    #region .ToXml
    /// <summary>
    /// Converts an anonymous type to an XElement.
    /// </summary>
    /// <param name="input">The input.</param>
    /// <returns>Returns the object as it's XML representation in an XElement.</returns>
    public static XElement ToXml(this object input) 
        return input.ToXml(null);
    

    /// <summary>
    /// Converts an anonymous type to an XElement.
    /// </summary>
    /// <param name="input">The input.</param>
    /// <param name="element">The element name.</param>
    /// <returns>Returns the object as it's XML representation in an XElement.</returns>
    public static XElement ToXml(this object input, string element) 
        return _ToXml(input, element);
    

    private static XElement _ToXml(object input, string element, int? arrayIndex = null, string arrayName = null) 
        if (input == null)
            return null;

        if (String.IsNullOrEmpty(element)) 
            string name = input.GetType().Name;
            element = name.Contains("AnonymousType") 
                ? "Object" 
                : arrayIndex != null
                    ? arrayName + "_" + arrayIndex
                    : name;
        

        element = XmlConvert.EncodeName(element);
        var ret = new XElement(element);

        if (input != null) 
            var type = input.GetType();
            var props = type.GetProperties();

            var elements = props.Select(p => 
                var pType = Nullable.GetUnderlyingType(p.PropertyType) ?? p.PropertyType;
                var name = XmlConvert.EncodeName(p.Name);
                var val = pType.IsArray ? "array" : p.GetValue(input, null);
                var value = pType.IsArray 
                    ? GetArrayElement(p, (Array)p.GetValue(input, null))
                    : pType.IsSimpleType() || pType.IsEnum 
                        ? new XElement(name, val) 
                        : val.ToXml(name);
                return value;
            )
            .Where(v=>v !=null);

            ret.Add(elements);
        

        return ret;
    

    #region helpers
    /// <summary>
    /// Gets the array element.
    /// </summary>
    /// <param name="info">The property info.</param>
    /// <param name="input">The input object.</param>
    /// <returns>Returns an XElement with the array collection as child elements.</returns>
    private static XElement GetArrayElement(PropertyInfo info, Array input) 
        var name = XmlConvert.EncodeName(info.Name);

        XElement rootElement = new XElement(name);

        var arrayCount = input == null ? 0 : input.GetLength(0);

        for (int i = 0; i < arrayCount; i++) 
            var val = input.GetValue(i);
            XElement childElement = val.GetType().IsSimpleType() ? new XElement(name + "_" + i, val) : _ToXml(val, null, i, name);

            rootElement.Add(childElement);
        

        return rootElement;
    

    #region .IsSimpleType
    public static bool IsSimpleType(this Type type) 
        return type.IsPrimitive || WriteTypes.Contains(type);
    
    #endregion .IsSimpleType

    #endregion helpers
    #endregion .ToXml

【讨论】:

这是一个不错的代码(已经+1),但它不能处理列表(列表必须转换为数组,.ToArray(),这并不总是可能的)。【参考方案4】:

我知道这是一篇旧帖子,但我的解决方案只用 2 行代码将匿名类型转换为 XML。

首先将您的匿名类型转换为 JSON,然后从 JSON 转换为 XML。

var jsonText = JsonConvert.SerializeObject(data);           // convert to JSON
XmlDocument doc = JsonConvert.DeserializeXmlNode(jsonText); // convert JSON to XML Document

示例

var data = new       // data - Anonymous Type

    Request = new
    
        OrderNumber = 123,
        Note = "Hello World"
    
;

var jsonText = JsonConvert.SerializeObject(data);           
XmlDocument doc = JsonConvert.DeserializeXmlNode(jsonText);

Console.WriteLine(doc.OuterXml);                            

输出

<Request>
    <OrderNumber>123</OrderNumber>
    <Note>Hello World</Note>
</Request>

【讨论】:

我正是在寻找这方面的东西。需要支付开销,但这是迄今为止最简单的解决方案。 +1【参考方案5】:

我自己的出色作品 @Matthew 和 @Martin 的自己的版本:现在支持枚举数组,并且将数组的概念推广到 IEnumerable 中,以便也支持所有类型的集合。

public static class ObjectExtensions 
/// <summary>
/// Converts an anonymous type to an XElement.
/// </summary>
/// <param name="input">The input.</param>
/// <returns>Returns the object as it's XML representation in an XElement.</returns>
public static XElement ToXml2(this object input) 
    return input.ToXml2(null);


/// <summary>
/// Converts an anonymous type to an XElement.
/// </summary>
/// <param name="input">The input.</param>
/// <param name="element">The element name.</param>
/// <returns>Returns the object as it's XML representation in an XElement.</returns>
public static XElement ToXml2(this object input, string element) 
    return _ToXml(input, element);


private static XElement _ToXml(object input, string element, int? arrayIndex = null, string arrayName = null) 
    if (input == null)
        return null;

    if (String.IsNullOrEmpty(element)) 
        string name = input.GetType().Name;
        element = name.Contains("AnonymousType") 
            ? "Object" 
            : arrayIndex != null
                ? arrayName + "_" + arrayIndex
                : name;
    

    element = XmlConvert.EncodeName(element);
    var ret = new XElement(element);

    if (input != null) 
        var type = input.GetType();
        var props = type.GetProperties();

        var elements = props.Select(p => 
            var pType = Nullable.GetUnderlyingType(p.PropertyType) ?? p.PropertyType;
            var name = XmlConvert.EncodeName(p.Name);
            var val = pType.IsArray ? "array" : p.GetValue(input, null);
            var value = pType.IsEnumerable()
                ? GetEnumerableElements(p, (IEnumerable)p.GetValue(input, null))
                : pType.IsSimpleType2() || pType.IsEnum 
                    ? new XElement(name, val) 
                    : val.ToXml2(name);
            return value;
        )
        .Where(v=>v !=null);

        ret.Add(elements);
    

    return ret;


#region helpers

private static XElement GetEnumerableElements(PropertyInfo info, IEnumerable input) 
    var name = XmlConvert.EncodeName(info.Name);

    XElement rootElement = new XElement(name);

    int i = 0;
    foreach(var v in input)
    
        XElement childElement = v.GetType().IsSimpleType2() || v.GetType().IsEnum ? new XElement(name + "_" + i, v) : _ToXml(v, null, i, name);
        rootElement.Add(childElement);
        i++;
    
    return rootElement;


private static readonly Type[] WriteTypes = new[] 
    typeof(string), typeof(DateTime), typeof(Enum), 
    typeof(decimal), typeof(Guid),
;
public static bool IsSimpleType2(this Type type) 
    return type.IsPrimitive || WriteTypes.Contains(type);


private static readonly Type[] FlatternTypes = new[] 
    typeof(string)
;
public static bool IsEnumerable(this Type type) 
    return typeof(IEnumerable).IsAssignableFrom(type) && !FlatternTypes.Contains(type);

#endregion

【讨论】:

【参考方案6】:

下面的答案以我需要的方式处理 IEnumerables 并将其变为:

new

    Foo = new[]
    
        new  Name = "One" ,
        new  Name = "Two" ,
    ,
    Bar = new[]
    
        new  Name = "Three" ,
        new  Name = "Four" ,
    ,

进入这个:

<object>
    <Foo><Name>One</Name></Foo>
    <Foo><Name>Two</Name></Foo>
    <Bar><Name>Three</Name></Bar>
    <Bar><Name>Four</Name></Bar>
</object>

所以你来了,马修回答的另一个变体:

public static class Tools

    private static readonly Type[] WriteTypes = new[] 
        typeof(string),
        typeof(Enum),
        typeof(DateTime), typeof(DateTime?),
        typeof(DateTimeOffset), typeof(DateTimeOffset?),
        typeof(int), typeof(int?),
        typeof(decimal), typeof(decimal?),
        typeof(Guid), typeof(Guid?),
    ;
    public static bool IsSimpleType(this Type type)
    
        return type.IsPrimitive || WriteTypes.Contains(type);
    
    public static object ToXml(this object input)
    
        return input.ToXml(null);
    
    public static object ToXml(this object input, string element)
    
        if (input == null)
            return null;

        if (string.IsNullOrEmpty(element))
            element = "object";
        element = XmlConvert.EncodeName(element);
        var ret = new XElement(element);

        if (input != null)
        
            var type = input.GetType();

            if (input is IEnumerable && !type.IsSimpleType())
            
                var elements = (input as IEnumerable<object>)
                    .Select(m => m.ToXml(element))
                    .ToArray();

                return elements;
            
            else
            
                var props = type.GetProperties();

                var elements = from prop in props
                               let name = XmlConvert.EncodeName(prop.Name)
                               let val = prop.GetValue(input, null)
                               let value = prop.PropertyType.IsSimpleType()
                                    ? new XElement(name, val)
                                    : val.ToXml(name)
                               where value != null
                               select value;

                ret.Add(elements);
            
        

        return ret;
    

【讨论】:

【参考方案7】:

我第一次为一个总是很有帮助的网站做贡献 Bellow 是一个完整的解决方案,可以与 .net core 一起使用(使用 3.1 测试) 只需调用 ConvertAnonymousToType.Convert("MyNewType", target, out Type newType) 返回将是一个对象,您可以使用它在控制器内部返回或手动序列化 (System.Xml.Serialization.XmlSerializer)。 使用对象作为返回在控制器内部: 1.配置服务(Startup.cs > ConfigureServices > services.AddMvcCore(options => options.OutputFormatters.Add(new XmlSerializerOutputFormatter());), 2. Target Action:使用[FormatFilter]和[Route(".......format")], 3.返回对象

如果任何额外的匿名类型需要组成回复(元数据、结果集等)DynamicTypeBuilder.CreateNewObject 可以用来做服务:)

再次感谢

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;


namespace Configuration

public static class DynamicTypeBuilder

    public static object CreateNewObject(string typeName, Dictionary<string, Type> properties, out Type newType)
    
        newType = CompileResultType(typeName, properties);
        return Activator.CreateInstance(newType);
    

    public static Type CompileResultType(string typeName, Dictionary<string, Type> properties)
    
        TypeBuilder tb = GetTypeBuilder(typeName);
        ConstructorBuilder constructor = tb.DefineDefaultConstructor(MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName);

        //Add properties
        properties.ToList().ForEach(p => CreateProperty(tb, p.Key, p.Value));

        //Created Type with properties
        Type objectType = tb.CreateType();
        return objectType;
    

    //Create Type with an standard configuration
    private static TypeBuilder GetTypeBuilder(string typeName)
    
        string assemblyName = typeName + "InternalAssembly";
        var an = new AssemblyName(assemblyName);
        AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
        ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule");
        TypeBuilder tb = moduleBuilder.DefineType(typeName,
                TypeAttributes.Public |
                TypeAttributes.Class |
                TypeAttributes.AutoClass |
                TypeAttributes.AnsiClass |
                TypeAttributes.BeforeFieldInit |
                TypeAttributes.AutoLayout,
                null);
        return tb;
    

    private static void CreateProperty(TypeBuilder tb, string propertyName, Type propertyType)
    
        FieldBuilder fieldBuilder = tb.DefineField("_" + propertyName, propertyType, FieldAttributes.Private);

        PropertyBuilder propertyBuilder = tb.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);
        MethodBuilder getPropMthdBldr = tb.DefineMethod("get_" + propertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, propertyType, Type.EmptyTypes);
        ILGenerator getIl = getPropMthdBldr.GetILGenerator();

        getIl.Emit(OpCodes.Ldarg_0);
        getIl.Emit(OpCodes.Ldfld, fieldBuilder);
        getIl.Emit(OpCodes.Ret);

        MethodBuilder setPropMthdBldr =
            tb.DefineMethod("set_" + propertyName,
              MethodAttributes.Public |
              MethodAttributes.SpecialName |
              MethodAttributes.HideBySig,
              null, new[]  propertyType );

        ILGenerator setIl = setPropMthdBldr.GetILGenerator();
        Label modifyProperty = setIl.DefineLabel();
        Label exitSet = setIl.DefineLabel();

        setIl.MarkLabel(modifyProperty);
        setIl.Emit(OpCodes.Ldarg_0);
        setIl.Emit(OpCodes.Ldarg_1);
        setIl.Emit(OpCodes.Stfld, fieldBuilder);

        setIl.Emit(OpCodes.Nop);
        setIl.MarkLabel(exitSet);
        setIl.Emit(OpCodes.Ret);

        propertyBuilder.SetGetMethod(getPropMthdBldr);
        propertyBuilder.SetSetMethod(setPropMthdBldr);
    


public static class ConvertAnonymousToType

    public static object Convert(string typeName, object target, out Type newType)
    
        var properties = GetProperties(target);
        newType = DynamicTypeBuilder.CompileResultType(typeName, properties);
        return Convert(newType, target);
    

    public static object Convert(Type type, object target)
    
        if (target.GetType().Name == typeof(List<>).Name)
        
            var newListType = typeof(List<>).MakeGenericType(type);
            var newList = Activator.CreateInstance(newListType);
            MethodInfo addMethod = newList.GetType().GetMethod("Add");
            ((IList<object>)target).ToList().ForEach(e =>
            
                addMethod.Invoke(newList, new object[]  ConvertObject(type, e) );
            );
            return newList;
        
        else
        
            return ConvertObject(type, target);
        
    

    private static object ConvertObject(Type type, object refObject)
    
        Dictionary<string, Type> properties = new Dictionary<string, Type>();
        object newObject = Activator.CreateInstance(type);
        var propertiesOrg = refObject.GetType().GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).ToList();
        var propertiesDes = newObject.GetType().GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).ToList();
        propertiesOrg.ForEach(po => propertiesDes.First(pd => pd.Name == po.Name).SetValue(newObject, po.GetValue(refObject)));
        return newObject;
    

    private static Dictionary<string, Type> GetProperties(object target)
    
        object objectRef = target;
        if (target.GetType().Name == typeof(List<>).Name) objectRef = ((List<object>)target).ToList()[0];

        Dictionary<string, Type> properties = new Dictionary<string, Type>();
        var lstProperties = objectRef.GetType().GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).ToList();
        lstProperties.ForEach(p => properties.Add(p.Name, p.PropertyType));
        return properties;
    



【讨论】:

【参考方案8】:

我发现这个页面非常有用。我正在从旧的 SOAP Web 引用转移到 .net 标准中支持的东西。我已经更改了代码,以便能够以可与 SOAP Web 服务一起使用的格式对其进行序列化。它很粗糙,但是它可以将object[] 转换为&lt;anyType /&gt; 元素,其他简单的数组也可以正确序列化,您可以传入命名空间或让它使用模型上的XmlTypeAttribute 来分配命名空间。

public static class AnonymousTypeSerializer

    private static XNamespace _xmlInstanceNs = "http://www.w3.org/2001/XMLSchema-instance";

    private static readonly Type[] WriteTypes = new[] 
        typeof(string), typeof(DateTime), typeof(Enum),
        typeof(decimal), typeof(Guid),
    ;

    private static readonly Dictionary<Type, string> SerializedTypeNames = new Dictionary<Type, string>()
    
         typeof(int), "int" ,
         typeof(long), "long" 
    ;

    /// <summary>
    /// Converts an anonymous type to an XElement.
    /// </summary>
    /// <param name="input">The input.</param>
    /// <returns>Returns the object as it's XML representation in an XElement.</returns>
    public static XElement ToXml(this object input)
    
        return input.ToXml(null);
    

    /// <summary>
    /// Converts an anonymous type to an XElement.
    /// </summary>
    /// <param name="input">The input.</param>
    /// <param name="element">The element name.</param>
    /// <returns>Returns the object as it's XML representation in an XElement.</returns>
    public static XElement ToXml(this object input, string element, XNamespace ns = null)
    
        return _ToXml(input, element, ns ?? XNamespace.None);
    

    private static XElement _ToXml(object input, string element, XNamespace ns = null, int? arrayIndex = null, string arrayName = null)
    
        if (input == null)
            return null;

        if (string.IsNullOrEmpty(element))
        
            string name = input.GetType().Name;
            element = name.Contains("AnonymousType")
                ? "Object"
                : arrayIndex != null
                    ? $"arrayName"
                    : name;
        

        element = XmlConvert.EncodeName(element);
        XElement ret = new XElement(ns.GetName(element));

        if (input != null)
        
            var type = input.GetType();
            var props = type.GetProperties();
            var xmlType = type.GetCustomAttribute<XmlTypeAttribute>(true);

            if (xmlType != null && xmlType.Namespace != null)
                ns = xmlType.Namespace;

            ret.Add(props.Select(p =>
            
                var pType = Nullable.GetUnderlyingType(p.PropertyType) ?? p.PropertyType;
                var name = XmlConvert.EncodeName(p.Name);

                var val = pType.IsArray ? "array" : p.GetValue(input, null);
                var value = pType.IsArray
                    ? GetArrayElement(p, (Array)p.GetValue(input, null), ns)
                    : pType.IsSimpleType() || pType.IsEnum
                        ? new XElement(ns.GetName(name), val)
                        : val.ToXml(name, ns);

                return value;
            ).Where(v => v != null));
        

        return ret;
    

    /// <summary>
    /// Gets the array element.
    /// </summary>
    /// <param name="info">The property info.</param>
    /// <param name="input">The input object.</param>
    /// <returns>Returns an XElement with the array collection as child elements.</returns>
    private static XElement GetArrayElement(PropertyInfo info, Array input, XNamespace ns)
    
        var name = XmlConvert.EncodeName(info.Name);
        var arrayCount = input == null ? 0 : input.GetLength(0);
        var elementType = input.GetType().GetElementType();
        var isAnyType = elementType == typeof(object);

        XElement rootElement;
        if (isAnyType)
            rootElement = new XElement(ns + name, new XAttribute(XNamespace.Xmlns + "xsi", _xmlInstanceNs));
        else
            rootElement = new XElement(ns + name);

        for (int i = 0; i < arrayCount; i++)
        
            var val = input.GetValue(i);

            if (isAnyType)
            
                var valType = val.GetType();
                var xmlType = valType.GetCustomAttribute<XmlTypeAttribute>(true);
                XNamespace valNs = ns;

                if (xmlType != null && xmlType.Namespace != null)
                    valNs = xmlType.Namespace;

                // Create anyType element
                var childElement = new XElement(ns + "anyType", new XAttribute(XNamespace.Xmlns + $"prootElement.Elements().Count()", valNs));

                // Create type attribute
                var nsPrefix = childElement.GetPrefixOfNamespace(valNs);
                childElement.Add(new XAttribute(_xmlInstanceNs + "type",
                    !string.IsNullOrEmpty(nsPrefix)
                        ? $"nsPrefix:valType.Name"
                        : valType.Name));

                // Create child elements
                var inner = _ToXml(val, null, ns, i, "anyType");
                childElement.Add(inner.Elements());

                // Done
                rootElement.Add(childElement);
            
            else
            
                if (!SerializedTypeNames.TryGetValue(elementType, out var typeName))
                    typeName = elementType.Name;

                rootElement.Add(elementType.IsSimpleType()
                    ? new XElement(ns.GetName(typeName.ToLower()), val)
                    : _ToXml(val, null, ns, i, typeName));
            
        

        return rootElement;
    

    public static bool IsSimpleType(this Type type)
    
        return type.IsPrimitive || WriteTypes.Contains(type);
    

【讨论】:

【参考方案9】:

另一张海报 Pavel 提到使用 Newtonsoft.Json 方法进行快速 2 行转换

var jsonText = JsonConvert.SerializeObject(data);           
XmlDocument doc = JsonConvert.DeserializeXmlNode(jsonText);

我建议这实际上可以是一行代码:)

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

XmlDocument doc = JsonConvert.DeserializeXmlNode(JsonConvert.SerializeObject(data));

【讨论】:

以上是关于我可以将匿名类型序列化为 xml 吗?的主要内容,如果未能解决你的问题,请参考以下文章

如何将 C# 匿名类型序列化为 JSON 字符串?

C# 是否可以将 动态或匿名类型 转成 强类型 ?

如何测试一个类型是不是是匿名的? [复制]

将 JSON 反序列化为匿名对象

WebAPI 返回匿名类型

使用 System.Text.Json 反序列化匿名类型