SharpSerializer:从反序列化中忽略属性/属性

Posted

技术标签:

【中文标题】SharpSerializer:从反序列化中忽略属性/属性【英文标题】:SharpSerializer: Ignore attributes/properties from deserialization 【发布时间】:2021-12-18 18:00:38 【问题描述】:

我正在使用SharpSerializer 序列化/反序列化对象。

我希望能够在反序列化时忽略特定属性。

SharpSerializer 可以选择按属性或按类和属性名称忽略属性:

SharpSerializerSettings.AdvancedSettings.AttributesToIgnore
SharpSerializerSettings.AdvancedSettings.PropertiesToIgnore

但似乎这些设置仅用于从 序列化 中忽略,而不是从 反序列化 中忽略(我使用 GitHub 源代码和 NugetPackage 进行了测试)。

我说的对吗?

有什么方法可以忽略反序列化的属性/属性?

附言

    我敢肯定还有其他很棒的序列化库,但是要更改代码和所有现有的序列化文件需要花费大量精力。 我在 GitHub 项目上开了一个issue,但该项目似乎从 2018 年开始就没有活动了。 具有要忽略的属性的对象不必是根对象。

【问题讨论】:

有问题的对象总是根对象吗? @dbc: 不,它不是根对象 【参考方案1】:

SharpSerializer 在反序列化时没有实现忽略属性值是正确的。这可以从ObjectFactory.fillProperties(object obj, IEnumerable<Property> properties)的参考源验证:

private void fillProperties(object obj, IEnumerable<Property> properties)

    foreach (Property property in properties)
    
        PropertyInfo propertyInfo = obj.GetType().GetProperty(property.Name);
        if (propertyInfo == null) continue;

        object value = CreateObject(property);
        if (value == null) continue;

        propertyInfo.SetValue(obj, value, _emptyObjectArray);
    

此代码使用反射无条件将从序列化流中读取的任何属性设置到传入对象中,而不检查被忽略的属性或属性列表。

因此,忽略所需属性的唯一方法似乎是创建自己的 XmlPropertyDeserializerBinaryPropertyDeserializer 版本,以跳过或过滤不需要的属性。以下是 XML 的一种可能实现。此实现照常将 XML 中的属性读取到 Property 层次结构中,然后应用过滤器操作以删除与应用了自定义属性 [SharpSerializerIgnoreForDeserialize] 的 .NET 属性相对应的属性,最后使用修剪后的 Property 创建对象树.

[System.AttributeUsage(System.AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class SharpSerializerIgnoreForDeserializeAttribute : System.Attribute  

public class PropertyDeserializerDecorator : IPropertyDeserializer

    readonly IPropertyDeserializer deserializer;
    public PropertyDeserializerDecorator(IPropertyDeserializer deserializer) => this.deserializer = deserializer ?? throw new ArgumentNullException();

    public virtual void Open(Stream stream) => deserializer.Open(stream);
    public virtual Property Deserialize() => deserializer.Deserialize();
    public virtual void Close() => deserializer.Close();


public class CustomPropertyDeserializer : PropertyDeserializerDecorator

    Action<Property> deserializePropertyAction;
    public CustomPropertyDeserializer(IPropertyDeserializer deserializer, Action<Property> deserializePropertyAction = default) : base(deserializer) => this.deserializePropertyAction = deserializePropertyAction;
    public override Property Deserialize()
    
        var property = base.Deserialize();

        if (deserializePropertyAction != null)
            property.WalkProperties(p => deserializePropertyAction(p));
        
        return property;
    


public static partial class SharpSerializerExtensions

    public static SharpSerializer Create(SharpSerializerXmlSettings settings, Action<Property> deserializePropertyAction = default)
    
        // Adapted from https://github.com/polenter/SharpSerializer/blob/42f9a20b3934a7f2cece356cc8116a861cec0b91/SharpSerializer/SharpSerializer.cs#L139
        // By https://github.com/polenter
        var typeNameConverter = settings.AdvancedSettings.TypeNameConverter ??
                                               new TypeNameConverter(
                                                   settings.IncludeAssemblyVersionInTypeName,
                                                   settings.IncludeCultureInTypeName,
                                                   settings.IncludePublicKeyTokenInTypeName);
        // SimpleValueConverter
        var simpleValueConverter = settings.AdvancedSettings.SimpleValueConverter ?? new SimpleValueConverter(settings.Culture, typeNameConverter);
        // XmlWriterSettings
        var xmlWriterSettings = new XmlWriterSettings
        
            Encoding = settings.Encoding,
            Indent = true,
            OmitXmlDeclaration = true,
        ;
        // XmlReaderSettings
        var xmlReaderSettings = new XmlReaderSettings
        
            IgnoreComments = true,
            IgnoreWhitespace = true,
        ;
        
        // Create Serializer and Deserializer
        var reader = new DefaultXmlReader(typeNameConverter, simpleValueConverter, xmlReaderSettings);
        var writer = new DefaultXmlWriter(typeNameConverter, simpleValueConverter, xmlWriterSettings);

        var _serializer = new XmlPropertySerializer(writer);
        var _deserializer = new CustomPropertyDeserializer(new XmlPropertyDeserializer(reader), deserializePropertyAction);
        
        var serializer = new SharpSerializer(_serializer, _deserializer)
        
            //InstanceCreator = settings.InstanceCreator ?? new DefaultInstanceCreator(), -- InstanceCreator not present in SharpSerializer 3.0.1 
            RootName = settings.AdvancedSettings.RootName,
        ;
        serializer.PropertyProvider.PropertiesToIgnore = settings.AdvancedSettings.PropertiesToIgnore;
        serializer.PropertyProvider.AttributesToIgnore = settings.AdvancedSettings.AttributesToIgnore;
        
        return serializer;
    
    
    public static void WalkProperties(this Property property, Action<Property> action)
    
        if (action == null || property == null)
            throw new ArgumentNullException();

        // Avoid cyclic dependencies.
        // Reference.IsProcessed is true only for the first reference of an object.
        bool skipProperty = property is ReferenceTargetProperty refTarget
            && refTarget.Reference != null
            && !refTarget.Reference.IsProcessed;

        if (skipProperty) return;

        action(property);

        switch (property.Art)
        
            case PropertyArt.Collection:
                
                    foreach (var item in ((CollectionProperty)property).Items)
                        item.WalkProperties(action);
                
                break;
            case PropertyArt.Complex:
                
                    foreach (var item in ((ComplexProperty)property).Properties)
                        item.WalkProperties(action);
                
                break;
            case PropertyArt.Dictionary:
                
                    foreach (var item in ((DictionaryProperty)property).Items)
                    
                        item.Key.WalkProperties(action);
                        item.Value.WalkProperties(action);
                    
                
                break;
            case PropertyArt.MultiDimensionalArray:
                
                    foreach (var item in ((MultiDimensionalArrayProperty )property).Items)
                        item.Value.WalkProperties(action);
                
                break;
            case PropertyArt.Null:
            case PropertyArt.Simple:
            case PropertyArt.Reference:
                break;
            case PropertyArt.SingleDimensionalArray:
                
                    foreach (var item in ((SingleDimensionalArrayProperty)property).Items)
                        item.WalkProperties(action);
                
                break;
            default:
                throw new NotImplementedException(property.Art.ToString());
        
    
    
    public static void RemoveIgnoredChildProperties(Property p)
    
        if (p.Art == PropertyArt.Complex)
        
            var items = ((ComplexProperty)p).Properties;
            for (int i = items.Count - 1; i >= 0; i--)
            
                if (p.Type.GetProperty(items[i].Name)?.IsDefined(typeof(SharpSerializerIgnoreForDeserializeAttribute), true) == true)
                
                    items.RemoveAt(i);
                
            
        
    

然后,给定以下模型:

public class Root

    public List<Model> Models  get; set;  = new ();


public class Model

    public string Value  get; set; 
    
    [SharpSerializerIgnoreForDeserialize]
    public string IgnoreMe  get; set; 

您将使用自定义的XmlPropertyDeserializer 进行反序列化,如下所示:

var settings = new SharpSerializerXmlSettings();
var customSerialzier = SharpSerializerExtensions.Create(settings, SharpSerializerExtensions.RemoveIgnoredChildProperties);
var deserialized = (Root)customSerialzier.Deserialize(stream);

如果您需要二进制反序列化,请使用以下工厂方法来创建序列化程序:

public static partial class SharpSerializerExtensions

    public static SharpSerializer Create(SharpSerializerBinarySettings settings, Action<Property> deserializePropertyAction = default)
    
        // Adapted from https://github.com/polenter/SharpSerializer/blob/42f9a20b3934a7f2cece356cc8116a861cec0b91/SharpSerializer/SharpSerializer.cs#L168
        // By https://github.com/polenter
        var typeNameConverter = settings.AdvancedSettings.TypeNameConverter ??
                                               new TypeNameConverter(
                                                   settings.IncludeAssemblyVersionInTypeName,
                                                   settings.IncludeCultureInTypeName,
                                                   settings.IncludePublicKeyTokenInTypeName);

        // Create Serializer and Deserializer
        Polenter.Serialization.Advanced.Binary.IBinaryReader reader;
        Polenter.Serialization.Advanced.Binary.IBinaryWriter writer;
        if (settings.Mode == BinarySerializationMode.Burst)
        
            // Burst mode
            writer = new BurstBinaryWriter(typeNameConverter, settings.Encoding);
            reader = new BurstBinaryReader(typeNameConverter, settings.Encoding);
        
        else
        
            // Size optimized mode
            writer = new SizeOptimizedBinaryWriter(typeNameConverter, settings.Encoding);
            reader = new SizeOptimizedBinaryReader(typeNameConverter, settings.Encoding);
        
        
        var _serializer = new BinaryPropertySerializer(writer);
        var _deserializer = new CustomPropertyDeserializer(new BinaryPropertyDeserializer(reader), deserializePropertyAction);
        
        var serializer = new SharpSerializer(_serializer, _deserializer)
        
            //InstanceCreator = settings.InstanceCreator ?? new DefaultInstanceCreator(), -- InstanceCreator not present in SharpSerializer 3.0.1 
            RootName = settings.AdvancedSettings.RootName,
        ;
        serializer.PropertyProvider.PropertiesToIgnore = settings.AdvancedSettings.PropertiesToIgnore;
        serializer.PropertyProvider.AttributesToIgnore = settings.AdvancedSettings.AttributesToIgnore;
        
        return serializer;
    

然后做:

var settings = new SharpSerializerBinarySettings();
var customSerialzier = SharpSerializerExtensions.Create(settings, SharpSerializerExtensions.RemoveIgnoredChildProperties);
var deserialized = (Root)customSerialzier.Deserialize(stream);

注意事项:

SharpSerializerExtensions.Create() 方法是在 SharpSerializer.initialize(SharpSerializerXmlSettings settings)SharpSerializer.initialize(SharpSerializerBinarySettings settings) 的基础上建模的 Pawel Idzikowski

nuget 上提供的SharpSerializer 版本version 3.0.1 仅包括commits 到2017 年10 月8 日。此后添加 the ability to use Autofac as the instance creator 的提交不能通过 nuget 获得。我的代码基于通过 nuget 提供的版本,因此不会初始化 2018 年添加的 SharpSerializer.InstanceCreator。从那时起,该项目似乎根本没有更新。

SharpSerializer.Deserialize() 反序列化为 序列化流 中指定的类型,而不是调用者指定的类型。因此,它似乎容易受到 Alvaro Muñoz 和 Oleksandr Mirosh 的黑帽论文 https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf 中描述的类型注入攻击。

有关详细信息,请参阅例如TypeNameHandling caution in Newtonsoft Json.

如果您愿意自己 fork、修改和构建 SharpSerializer,您可以考虑更新 ObjectFactory.fillProperties(object obj, IEnumerable&lt;Property&gt; properties) 以不设置忽略的属性。

演示小提琴 #1 here 用于 XML,#2 here 用于二进制。

【讨论】:

谢谢!这很有帮助!我遇到了一个对象具有循环依赖关系的问题。我解决了它并相应地在您的答案中编辑了WalkProperties 方法。

以上是关于SharpSerializer:从反序列化中忽略属性/属性的主要内容,如果未能解决你的问题,请参考以下文章

从反序列化漏洞到掌控帝国:百万美刀的Instagram漏洞

Jackson(使用注解)

SQL 从反透视表中删除过滤器

使用非标准构造函数序列化和反序列化对象 - protobuf-net

从反汇编的角度学C/C++之条件判断

从反汇编的角度学C/C++之条件判断