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.
- 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.
- I opened an issue on the GitHub project, but the project does not seem to be active since 2018.
- 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:
The methods
SharpSerializerExtensions.Create()
were modeled onSharpSerializer.initialize(SharpSerializerXmlSettings settings)
andSharpSerializer.initialize(SharpSerializerBinarySettings settings)
by Pawel IdzikowskiThe version of
SharpSerializer
available on nuget, version 3.0.1, only includes commits through 10/8/2017. Submissions since then that add the ability to use Autofac as the instance creator are not available via nuget. My code is based on the version available via nuget, and thus does not initializeSharpSerializer.InstanceCreator
which was added in 2018. The project appears not to have updated at all since then.SharpSerializer.Deserialize()
deserializes to the type specified in the serialization stream rather than to a type specified by the caller. It thus appears vulnerable to the sort of type injection attacks described in Alvaro Muñoz & Oleksandr Mirosh's blackhat paper https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf.For details see e.g. TypeNameHandling caution in Newtonsoft Json.
If you are willing to fork, modify and build
SharpSerializer
yourself, you might consider updatingObjectFactory.fillProperties(object obj, IEnumerable<Property> properties)
to not set ignored properties.