Search code examples
c#xmlserializationdeserializationsharpserializer

SharpSerializer: Ignore attributes/properties from deserialization


I am using SharpSerializer to serialize/deserialize object.

I want the ability to ignore specific properties when deserializing.

SharpSerializer has an option to ignore properties by attribute or by classes and property name:

SharpSerializerSettings.AdvancedSettings.AttributesToIgnore
SharpSerializerSettings.AdvancedSettings.PropertiesToIgnore

but it seems that these settings are only used to ignore from serialization, not from deserialization (I tested with the GitHub source code and the NugetPackage).

Am I correct?

Is there any way to ignore attributes/properties from deserialization?

P.S.

  1. I'm sure there are other great serialization libraries, but it will take a great amount of effort to change the code and all the existing serialized files.
  2. I opened an issue on the GitHub project, but the project does not seem to be active since 2018.
  3. The object with properties to ignore need not be the root object.

Solution

  • You are correct that SharpSerializer does not implement ignoring of property values when deserializing. This can be verified from the reference source for 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);
        }
    }
    

    This code unconditionally sets any property read from the serialization stream into the incoming object using reflection, without checking the list of ignored attributes or properties.

    Thus the only way to ignore your desired properties would seem to be to create your own versions of XmlPropertyDeserializer or BinaryPropertyDeserializer that skip or filter the unwanted properties. The following is one possible implementation for XML. This implementation reads the properties from XML into a Property hierarchy as usual, then applies a filter action to remove properties corresponding to .NET properties with a custom attribute [SharpSerializerIgnoreForDeserialize] applied, then finally creates the object tree using the pruned 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);
                    }
                }
            }
        }
    }
    

    Then, given the following models:

    public class Root
    {
        public List<Model> Models { get; set; } = new ();
    }
    
    public class Model
    {
        public string Value { get; set; }
        
        [SharpSerializerIgnoreForDeserialize]
        public string IgnoreMe { get; set; }
    }
    

    You would deserialize using the customized XmlPropertyDeserializer as follows:

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

    If you need binary deserialization, use the following factory method to create the serializer instead:

    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;
        }
    }
    

    And do:

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

    Notes:

    Demo fiddle #1 here for XML, and #2 here for binary.