Home > front end >  SharpSerializer: Ignore attributes/properties from deserialization
SharpSerializer: Ignore attributes/properties from deserialization

Time:11-09

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.

CodePudding user response:

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();
        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.

  • Related