将属性进行序列化和反序列化,属性名根据特性中给定的类型动态分配

前端开发 2026-07-12

我在使用Newtonsoft.Json。

我已经有一些从XSD转换成的C#类。

在这些类中,有一些属性的名称在XML序列化时应根据属性中指示的类型动态改变。

// inside a broader class
[System.Xml.Serialization.XmlElementAttribute("ReportRequest", typeof(ReportRequestType), Form = System.Xml.Schema.XmlSchemaForm.Unqualified, ElementName = "ReportRequest")]       [System.Xml.Serialization.XmlElementAttribute("StatusRequest", typeof(StatusRequestType), Form = System.Xml.Schema.XmlSchemaForm.Unqualified, ElementName = "StatusRequest")]
[System.Xml.Serialization.XmlElementAttribute("TransmitRequest", typeof(TransmitRequestType), Form = System.Xml.Schema.XmlSchemaForm.Unqualified, ElementName = "TransmitRequest")]
public object Item
{
    get
    {
        return this.itemField;
    }
    set
    {
        this.itemField = value;
    }
}

使用Newtonsoft.Json进行序列化时,属性名称是Item,而在XML中,例如如果生成的类型是TransmitRequestType,则对应的名称为TransmitRequest。

我尝试对Item应用一个自定义的JsonConverter,但在这种情况下似乎无法访问属性来根据Item的类型确定要使用的名称。

我尝试了ContractResolver,但那样我可以访问属性,但似乎无法知道将实际使用的类型。

我可能漏掉了什么,但我不知道是什么,也不知道问题出在哪里。

我期望得到的是类似于

来自:

class A { public string Value {get;set;}}
class B { public int B1 {get; set;} public string B2 {get; set;}}

[attribute..., MyType=typeof(A), MyName="A"]
[attribute..., MyType=typeof(B), MyName="B"]
public object Item {get;set;}

如果我创建一个对象,其中Item的类型是A,在序列化时我得到的名称来自属性

{
...
"A": {... content of class A},
...
}

如果我创建一个对象,其中Item的类型是B,在序列化时我得到的名称来自属性

{
...
"B": {... content of class B},
...
}

注:

  1. 不要相信这些类、类型、属性本身……它们只是用来描述问题的示例。
  2. 我的实际类型还包含许多其他属性,它们应用了Json.NET的 [serialization attributes] 来控制它们的名称、是否忽略,等等。Json.NET的默认contract resolver会考虑到这些,我希望避免从头重新做所有这些工作。

解决方案

你希望有一种使用Json.NET序列化模型的方法,能够像 XmlSerializer 那样处理多态属性:

  1. 对于每个属性,可能的多态子类型集合在编译时已知,可以通过一系列属性来指定,类似于 [XmlElementAttribute.Type](https://learn.microsoft.com/en-us/dotnet/api/system.xml.serialization.xmlelementattribute.type) 的工作方式:

public partial class Model { [XmlElement("A", typeof(A)), XmlElement("B", typeof(B))] public object Item { get; set; } } 2.当将多态属性序列化为JSON时,使用的名称应等于当前正在序列化的实际、具体类型在属性列表中指定的名称。 3.在反序列化时,当遇到多态属性名称时,JSON中的值应被反序列化为由属性指定的类型。

鉴于这些要求,使用诸如Brian Rogers的 JsonConverter 的方案(如 [How to change property names depending on the type when serializing with Json.net?] 的链接)将不太方便。相反,我建议创建一个 自定义契约解析器,继承自 [DefaultContractResolver],为每个多态属性对应的可能值类型添加适当重命名的综合属性,然后使用 条件序列化 仅序列化当前值类型对应的单个属性。然后在反序列化时,序列化器将能够从综合属性名称自动推断类型。

首先引入以下属性:

[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
public sealed class JsonPolymorphicNameAttribute : System.Attribute
{
    public JsonPolymorphicNameAttribute(string polymorphicName, Type polymorphicType)
    {
        this.PolymorphicName = polymorphicName;
        this.PolymorphicType = polymorphicType;
    }

    public string PolymorphicName { get; private set; }
    public Type PolymorphicType { get; private set; }
}

接着创建以下契约解析器:

public class JsonPolymorphicNameAttributeResolver : DefaultContractResolver
{
    protected override JsonObjectContract CreateObjectContract(Type objectType)
    {
        var contract = base.CreateObjectContract(objectType);

        for (int i = 0; i < contract.Properties.Count; i++)
        {
            var property = contract.Properties[i];
            var attributeProvider = property.AttributeProvider;
            var valueProvider = property.ValueProvider;
            if (attributeProvider == null || valueProvider == null || property.Ignored)
                continue;
            var attrs = attributeProvider.GetAttributes(typeof(JsonPolymorphicNameAttribute), true);
            if (attrs.Count == 0)
                continue;
            if (property.PropertyName != null && contract.CreatorParameters.GetClosestMatchProperty(property.PropertyName) != null)
            {
                // TODO: decide how to handle polymorphic property names in parameterized objects where the polymorphically named property needs to be passed to the constructor.  
                // Unfortunately Json.NET matches constructor parameters to JSON properties by name which is inconsistent with polymorphic property naming.
                throw new ArgumentException(string.Format("Polymorphically named properties are not supported for parameterized constructors: property \"{0}\", type \"{1}\"", property.PropertyName, contract.UnderlyingType));
            }
            var polymorphicTypes = new HashSet<Type>(attrs.Count);
            foreach (var attr in attrs.Cast<JsonPolymorphicNameAttribute>())
            {
                if (property.PropertyType != null && !property.PropertyType.IsAssignableFrom(attr.PolymorphicType))
                {
                    throw new ArgumentException(string.Format("JsonProperty.PropertyType {0} is not assignable from JsonPolymorphicNameAttribute.PolymorphicType {1}", property.PropertyType, attr.PolymorphicType));
                }
                var newProperty = property.ShallowClone();
                newProperty.PropertyName = attr.PolymorphicName;
                newProperty.PropertyType = attr.PolymorphicType;
                if (property.Readable)
                {
                    newProperty.ShouldSerialize = newProperty.ShouldSerialize.And(
                        o =>
                        {
                            var value = valueProvider.GetValue(o);
                            return value != null && value.GetType() == attr.PolymorphicType;
                        });
                }
                else
                {
                    newProperty.ShouldSerialize = o => false;
                }
                contract.Properties.Insert(++i, newProperty);
                polymorphicTypes.Add(attr.PolymorphicType);
            }
            property.ShouldSerialize = property.ShouldSerialize.And(
                o =>
                {
                    var value = valueProvider.GetValue(o);
                    // TODO: decide what to do if the value is null, since we can't get the concretetype.
                    return value != null && !polymorphicTypes.Contains(value.GetType());
                });
        }

        return contract;
    }
}

public static partial class JsonExtensions
{
    static readonly Func<JsonProperty, JsonProperty> ShallowClonePropertyFunc = CreateShallowCloneMethod<JsonProperty>();

    public static JsonProperty ShallowClone(this JsonProperty property) 
    {
        if (property == null)
            throw new ArgumentNullException("property");
        return ShallowClonePropertyFunc(property);
    }

    internal static Predicate<T> And<T>(this Predicate<T> first, Predicate<T> second)
    {
        if (second == null)
            return first;
        else if (first == null)
            return second;
        else return v => first(v) && second(v);
    }

    internal static Func<T, T> CreateShallowCloneMethod<T>()
    {
        var method = typeof(T).GetMethod("MemberwiseClone", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
        if (method == null)
            throw new ArgumentException(string.Format("No MemberwiseClone() method was found for type {0}", typeof(T)));
        var cloneUntyped = (Func<T, object>)Delegate.CreateDelegate(typeof(Func<T, object>), method);
        return delegate(T obj) { return (T)cloneUntyped(obj); };
    }
}

然后,要使用它,修改你的模型并按需应用 [JsonPolymorphicName]

public class Model
{
    // Other properties not shown in the question

    [JsonPolymorphicName("A", typeof(A))]
    [JsonPolymorphicName("B", typeof(B))]
    [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] // Other JsonPropertyAttribute settings which must needs be retained.
    public object Item { get; set; }
}

接着用你需要的命名策略构造一个 JsonPolymorphicNameAttributeResolver 的实例:

DefaultContractResolver resolver = new JsonPolymorphicNameAttributeResolver()
{
    NamingStrategy = new CamelCaseNamingStrategy(),
};

然后在序列化和反序列化一个 Model 的实例时,在 [JsonSerializerSettings.ContractResolver] 中使用它,如下所示:

var model = new Model
{
    Item = new B { B1 = 13, B2 = "value of B2" },
};

var settings = new JsonSerializerSettings
{
    ContractResolver = resolver,
    // Add any other settings you require
};

var json = JsonConvert.SerializeObject(model, settings);

var model2 = JsonConvert.DeserializeObject<Model>(json, settings);

现在模型将来回传输为 {"B":{"b1":13,"b2":"value of B2"}},并且在反序列化时类型将被正确推断。

注:

  • [JsonProperty.ValueProvider] 有一个方法 [IValueProvider.GetValue(object target)],用于返回特定实例的属性值。
  • 因此,在 [Predicate<object>] 指定的上下文中,通过 [JsonProperty.ShouldSerialize] 可以使用值提供者来获取当前属性值,以确定其类型,从而判断该属性是否应由当前的多态属性进行序列化。
  • 多态的 构造函数参数 不被支持。如果你的某个多态属性对应构造函数参数,则会抛出一个 ArgumentException
  • 我不确定当多态属性的值为null时你希望如何处理。在上面的代码中我禁用了序列化。
  • 为获得最佳性能,Newtonsoft建议缓存并重复使用你的契约解析器实例,详见 Performance
  • 我在上面的实现中使用了较旧的C#语法,以便你在.NET Framework上编码时不会有问题。
  • 我使用了自定义属性来指定多态名称,但如果你愿意,也可以改用 [XmlElementAttribute]。如要获取给定属性的特定类型的属性集合,你可以使用 [JsonProperty.AttributeProvider],也就是:

var attrs = property.AttributeProvider?.GetAttributes(typeof(System.Xml.Serialization.XmlElementAttribute), true);

演示.NET Framework fiddle在这里。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章