How can the interface-driven serialization framework be improved? - c#

I am developing an interface-driven serialization framework for use with interfaces for data XML serialization.
The ultimate goal is, that that framework is capable of save/load the state of the object (or a part of it, visible thought interface prism) using interfaces as an abstraction.
While thinking of the design, I have found, that using C# attributes, I can provide framework with required info to
save and load state of the object, and the way, how to perform a serialization/deserialization. As an advantages, I discovered that no solution exits, capable of partial object save/restore. If i describe the XML data in terms of an interface with data attributes, i have found that it is not requred anymore to have a dependency to that interface form the class (so that class does not require anymore to implement the given interface for the XML data).
I have a question about the usage of XmlObjectSerializer class, due to some usability issues. Here the example of the interface declaration, being tested:
Here is a primer:
interface IPersonRoot
{
string Name { get; set; }
}
[XmlRootSerializer("Body")]
interface IPersonCustomRoot
{
string Name { get; set; }
}
interface IPersonAttribute
{
[XmlAttributeRuntimeSerializer]
string Name { get; set; }
}
interface IPersonCustomAttribute
{
[XmlAttributeRuntimeSerializer("Id")]
string Name { get; set; }
}
interface IPersonElement
{
[XmlElementRuntimeSerializer]
string Name { get; set; }
}
interface IPersonCustomElement
{
[XmlElementRuntimeSerializer("Head")]
string Name { get; set; }
}
interface IPersonCustomElementString
{
[XmlElementRuntimeSerializer("Head", typeof(string))]
string Name { get; set; }
}
interface IVeryImportantPersonRoot : IPersonRoot
{
Guid Id { get; set; }
}
interface IVeryImportantPersonCustomRoot : IPersonCustomRoot
{
Guid Id { get; set; }
}
[XmlRootSerializer("Spirit")]
interface IVeryImportantPersonCustomRootOverride : IPersonCustomRoot
{
Guid Id { get; set; }
}
interface IVeryImportantPersonAttribute : IPersonAttribute
{
Guid Id { get; set; }
}
interface IVeryImportantPersonCustomAttribute : IPersonCustomAttribute
{
Guid Id { get; set; }
}
interface IVeryImportantPersonElement : IPersonElement
{
Guid Id { get; set; }
}
interface IVeryImportantPersonCustomElement : IPersonCustomElement
{
Guid Id { get; set; }
}
interface IVeryImportantPersonCustomElementOverride : IPersonCustomElement
{
[XmlElementRuntimeSerializer("Guid")]
Guid Id { get; set; }
}
interface IVeryImportantPersonCustomElementOverrideGuid : IPersonCustomElement
{
[XmlElementRuntimeSerializer("Guid", typeof(Guid))]
Guid Id { get; set; }
}
interface IVeryImportantPersonCustomRuntimeSerializer : IPersonCustomElement
{
Guid Id { get; set; }
[XmlColorRuntimeSerializer]
Color Color { get; set; }
}
interface IVeryImportantPersonColor
{
string Name { get; set; }
Guid Id { get; set; }
[XmlColorRuntimeSerializer]
Color Color { get; set; }
}
Here is the XmlObjectSerializer class definition:
public class XmlObjectSerializer
{
//...
public static void Load<T>(string xml, Type type, object value)
{
using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(xml)))
{
XDocument root = XDocument.Load(XmlReader.Create(ms));
string rootName = GetRootName(typeof(T), root.Root.Name.ToString());
IXmlObjectSerializer serializer = Load(root.Root, CreateInternal(null, rootName));
serializer.Deserialize(type, value);
}
}
public static void Load<T>(string xml, object value)
{
using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(xml)))
{
XDocument root = XDocument.Load(XmlReader.Create(ms));
string rootName = GetRootName(typeof(T), root.Root.Name.ToString());
IXmlObjectSerializer serializer = Load(root.Root, CreateInternal(null, rootName));
serializer.Deserialize(typeof(T), value);
}
}
public static string Save<T>(Type type, object value)
{
string rootName = GetRootName(typeof(T), value.GetType().Name);
XmlObjectSerializer result = CreateInternal(null, rootName);
IXmlRuntimeSerializer serializer = result;
serializer.Serialize(type, value);
XDocument root = new XDocument();
return result.ToXmlString();
}
public static string Save<T>(object value)
{
string rootName = GetRootName(typeof(T), value.GetType().Name);
XmlObjectSerializer result = CreateInternal(null, rootName);
IXmlRuntimeSerializer serializer = result;
serializer.Serialize(typeof(T), value);
return result.ToXmlString();
}
public static void Load(Stream data, Type type, object value)
{
XDocument document = XDocument.Load(XmlReader.Create(data));
string rootName = GetRootName(type, document.Root.Name.ToString());
IXmlObjectSerializer serializer = Load(document.Root, CreateInternal(null, rootName));
serializer.Deserialize(type, value);
}
public static void Save(Stream data, Type type, object value)
{
string rootName = GetRootName(type, value.GetType().Name);
XmlObjectSerializer serializer = CreateInternal(null, rootName);
serializer.Save(type, data, value);
}
}
Then, here is a question:
Is is better to change XmlObjectSerializer class to be more generic, like XmlObjectSerializer, or just hide all public methods, which uses an Type as as argument? I have the compiler static analyzer warning, saying that using a generic methods, (i.e. Load(...), Save(...)) in a non-generic class is a bad practices and should be changed to the method, accepting Type as an argument.
The problem is, should i change the design of a XmlObjectSerializer class following that rule (in this case, i will loose generic approach) and use object and Type as an arguments, or keep the methods intact.
Can you give me an advice to follow on, and code samples related to posted code sample?
Phanx,

I think that particular compiler warning in the general case is garbage; you should always look at the specific case.
Having written a few such frameworks, I would agree that it is important to include a Type based API, since there are many scenarios for such libraries where generics are a nuisance. By optimising for the non-generic case (rather than using MakeGenericMethod) you can do the other very simply:
Foo(Type type, ...) {...}
Foo<T>(...) { Foo(typeof(T), ...); }
This coming from someone currently re-writing a library to move the optimised case to Type rather than <T>. This is especially important for compact framework etc, where otherwise you can start getting missing-method exceptions when it runs out of space.
Re the interfaces; looks messy and complex - tl;dr; on that. But as long as you're happy...

Related

How Can I Parse YAML Into a Derived Collection Using YamlDotNet?

Using YamlDotNet, I am attempting to deserialize the following YAML:
Collection:
- Type: TypeA
TypeAProperty: value1
- Type: TypeB
TypeBProperty: value2
The Type property is a required property for all objects under Collection. The rest of the properties are dependent on the type.
This is my ideal object model:
public class Document
{
public IEnumerable<IBaseObject> Collection { get; set; }
}
public interface IBaseObject
{
public string Type { get; }
}
public class TypeAClass : IBaseObject
{
public string Type { get; set; }
public string TypeAProperty { get; set; }
}
public class TypeBClass : IBaseObject
{
public string Type { get; set; }
public string TypeBProperty { get; set; }
}
Based on my reading, I think my best bet is to use a custom node deserializer, derived from INodeDeserializer. As a proof of concept, I can do this:
public class MyDeserializer : INodeDeserializer
{
public bool Deserialize(IParser parser, Type expectedType, Func<IParser, Type, object> nestedObjectDeserializer, out object value)
{
if (expectedType == typeof(IBaseObject))
{
Type type = typeof(TypeAClass);
value = nestedObjectDeserializer(parser, type);
return true;
}
value = null;
return false;
}
}
My issue now is how to dynamically determine the Type to choose before calling nestedObjectDeserializer.
When using JSON.Net, I was able to use a CustomCreationConverter, read the sub-JSON into a JObject, determine my type, then create a new JsonReader from the JObject and re-parse the object.
Is there a way I can read, roll-back, then re-read nestedObjectDeserializer?
Is there another object type I can call on nestedObjectDeserializer, then from that read the Type property, finally proceed through normal YamlDotNet parsing of the derived type?
It's not easy. Here is an GitHub issue explaining how to do polymorphic serialization using YamlDotNet.
A simple solution in your case is to to 2-step deserialization. First you deserialize into some intermediary form and than convert it to your models. That's relatively easy as you limit digging in the internals of YamlDotNet:
public class Step1Document
{
public List<Step1Element> Collection { get; set; }
public Document Upcast()
{
return new Document
{
Collection = Collection.Select(m => m.Upcast()).ToList()
};
}
}
public class Step1Element
{
// Fields from TypeA and TypeB
public string Type { get; set; }
public string TypeAProperty { get; set; }
public string TypeBProperty { get; set; }
internal IBaseObject Upcast()
{
if(Type == "TypeA")
{
return new TypeAClass
{
Type = Type,
TypeAProperty = TypeAProperty
};
}
if (Type == "TypeB")
{
return new TypeBClass
{
Type = Type,
TypeBProperty = TypeBProperty
};
}
throw new NotImplementedException(Type);
}
}
And that to deserialize:
var serializer = new DeserializerBuilder().Build();
var document = serializer.Deserialize<Step1Document>(data).Upcast();

How to exclude properties from serialization based on their values?

To communicate with a web API, I have created a few classes which will be serialized to XML and sent to the API. The API only accepts these XMLs if they only hold properties with non-default values.
How can I leave out properties during serialization?
Suppose I have a class as follows (simplified example):
[XmlRoot("SomeData")]
public class SomeData
{
[XmlElement("rangeX")]
public int RangeX { get; set; }
[XmlElement("rangeY")]
public int RangeY { get; set; }
[XmlElement("rangeZ")]
public int RangeZ { get; set; }
}
An object which has a non-default value for RangeX and RangeY should thus be serialized to an XML which only holds tags for rangeX and rangeY.
I have found how to leave out null values, but this is not what I want - the default value could very well be different from null.
Thanks!
You can use the "secret" ShouldSerializeXxx() method:
public bool ShouldSerializeRangeX()
{
return RangeX != someDefaultValue;
}
There are a ton of examples when you search for ShouldSerialize Default Value, it's just hard to find if you don't know what you're looking for.
Here is a link to help get you started : Defining Default Values with the ShouldSerialize and Reset Methods
You can gain full control on how the objects of your class are serialized when you implement the IXmlSerializable interface (only including the code to write the data, since that is your immediate question):
public class SomeData : IXmlSerializable
{
public int RangeX { get; set; }
public int RangeY { get; set; }
public int RangeY { get; set; }
public void WriteXml (XmlWriter writer)
{
writer.WriteStartElement("SomeData");
if (RangeX != 0)
{
writer.WriteElementString("rangeX", RangeX.ToTring());
}
if (RangeY != 0)
{
writer.WriteElementString("rangeY", RangeY.ToTring());
}
if (RangeZ != 0)
{
writer.WriteElementString("rangeZ", RangeZ.ToTring());
}
writer.WriteEndElement();
}
public void ReadXml (XmlReader reader)
{
//Implement if needed
throw new NotImplementedException();
}
public XmlSchema GetSchema()
{
return null;
}
}

Returning an object derived from an interface with generic list

My application reads in JSON from disk and deserialising using JSON.net; which is working fine.
My JSON is laid out like this:
{
"driver": {
"driverTag": "blah_blah",
"driverName": "Blah Blah",
"driverTransport": "serial-device"
},
"devices": [
{
"deviceName": "Dev1",
"deviceTag": "DEV1",
"deviceStartMode": "Auto"
},
{
"deviceName": "Dev2",
"deviceTag": "DEV2",
"deviceStartMode": "Auto"
}
]
}
Based on the "driverTransport" value, I deserialise to either a SerialDriverConfig, TelnetDriverConfig, SNMPDriverConfig... etc class.
As the "driver" properties will be the same for every driver, no matter the transport type, I have a "DriverConfigTemplate" class. The "devices" will differ from JSON file to JSON file and have specific properties for that transport type (i.e. a serial device will have properties like "serialPortName", "serialBaudRate" etc.)
I have a "DriverConfig" interface, where T is "DeviceConfig".
public interface DriverConfig<T> where T : DeviceConfig
{
DriverConfigTemplate driver { get; set; }
List<T> devices { get; set; }
}
My device config is as follows:
public class DeviceConfig : IDeviceConfig
{
public string deviceTag { get; set; }
public string deviceName { get; set; }
public string deviceStartMode { get; set; }
}
Now; the problem part. When I am deserialising, I check the transport type before hand and determine the class to use; i.e for a serial driver I will use the "SerialDriverConfig" class and deserialise using the "SerialDeviceConfig":
public class SerialDeviceConfig : DeviceConfig
{
public int serialComPort { get; set; }
public int serialBaudRate { get; set; }
public int serialDataBits { get; set; }
public string serialParity { get; set; }
public string serialStopBits { get; set; }
public string serialHandshake { get; set; }
public int serialReadTimeout { get; set; }
public int serialWriteTimeout { get; set; }
public bool serialRtsEnable { get; set; }
public bool serialDtrEnable { get; set; }
}
My "SerialDriverConfig" class looks like this:
public class SerialDriverConfig : DriverConfig<SerialDeviceConfig>
{
public DriverConfigTemplate driver { get; set; }
public List<SerialDeviceConfig> devices { get; set; }
}
Again, this is fine and the JSON.net deserialiser does its job perfectly.
I have a function that gets called when the JSON config file has been loaded and validated against its respective schema, then passed on to a "DeserialiseDriverConfig" function where I am trying to return the derived driver object; which is where I am stuck :(
private DriverConfig<DeviceConfig> DeserialiseDriverConfig(string _json, string _driverTransport)
{
switch (_driverTransport)
{
case "serial-device":
try
{
SerialDriverConfig _serialDriverConfig = JsonConvert.DeserializeObject<SerialDriverConfig>(_json);
if (_serialDriverConfig != null)
{
return _serialDriverConfig;
}
}
catch (Exception e)
{
//Blah blah blah
}
break;
}
return null;
}
I have been stuck on this one for a few days, have tried many things and this is where I have ended up. I am getting "Cannot implicitly convert type "SerialDriverConfig" to "DriverConfig". An explicit conversion exists (are you missing a cast?)" So I understand why this error is occurring, but cannot get around it.
Hope my code makes sense and someone can help me out here?
You can change your DriverConfig class to be non-generic
public interface DriverConfig
{
DriverConfigTemplate driver { get; set; }
List<DeviceConfig> devices { get; set; }
}
and instead of using derived classes (SerialDriverConfig etc.) you can set Json.net to deserialize to the correct DeviceConfig type based on either having a $type attribute in your JSON like this or using a custom JsonConverter similar to this
I'm not sure if this solution fits your need but if you create your method and SerialDriverConfig with using generic type T you can use your interface as a returning type. Can you try the code below;
Your Method:
private static DriverConfig<T> DeserialiseDriverConfig<T>(string _json, string _driverTransport)
{
switch (_driverTransport)
{
case "serial-device":
try
{
SerialDriverConfig<T> _serialDriverConfig = JsonConvert.DeserializeObject<SerialDriverConfig<T>>(_json);
if (_serialDriverConfig != null)
{
return _serialDriverConfig;
}
}
catch (Exception e)
{
//Blah blah blah
}
break;
}
return null;
}
SerialDriverConfig Class:
public class SerialDriverConfig<T> : DriverConfig<T>
{
public DriverConfigTemplate driver { get; set; }
public List<T> devices { get; set; }
}
Also you should consider changing DriverConfig<T> interface approach because if you leave it as-is you will have boxing issue. If you do not need you may remove where T : DeviceConfig from your interface or modify it according to your current circumstances.
Hope this helps, please let me know if this works for you

How to map some source properties to a wrapped destination type using AutoMapper?

Suppose you have this source model:
public abstract class SourceModelBase {
}
public class SourceContact : SourceModelBase {
public string FirstName { get; set; }
public string LastName { get; set; }
public KeyValuePair Pair { get; set; }
public SourceAddress Address { get; set; }
}
public class KeyValuePair { // Not derived from SourceModelBase.
public string Key { get; set; }
public string Value { get; set; }
}
public class SourceAddress : SourceModelBase {
public string StreetName { get; set; }
public string StreetNumber { get; set; }
}
Now the destination model should be mapped 1:1 by default (subject to normal AutoMapper configuration), but each thing derived from SourceModelBase should be mapped to a wrapper class class Wrap<T> { T Payload { get; set; } string Meta { get; set; } }.
public abstract class DestinationModelBase {
}
public class DestinationContact : DestinationModelBase {
public string FirstName { get; set; }
public string LastName { get; set; }
public KeyValuePair Pair { get; set; } // Not wrapped, base class not `SourceModelBase`.
public Wrap<DestinationAddress> Address { get; set; }
}
public class DestinationAddress : DestinationModelBase {
public string StreetName { get; set; }
public string StreetNumber { get; set; }
}
Since the contact class itself is derived from SourceModelBase it should be wrapped as well.
The result should have this structure:
Wrap<DestinationContact> Contact
string Meta // Comes from the custom wrapper logic.
DestinationContact Payload
string FirstName
string LastName
KeyValuePair Pair
string Key
string Value
Wrap<DestinationAddress> Address
string Meta // Comes from the custom wrapper logic.
DestinationAddress Payload
string StreetName
string StreetNumber
Obviously this wrapping should nest, illustrated by the fact that the mapped object itself is subject to it and so is its Address property.
For some reason all I keep finding are questions related to mapping from destination to source. I know I have to somehow use ResolveUsing and if the destination type is derived from SourceModelBase, somehow apply custom logic to provide the Wrap<T> value based on the value of the source property.
I don't know where to start at all, though. Especially when the source object itself is specified to be subject of the wrapping logic as well.
What's the best, most AutoMapper-idiomatic way to wrap the nested objects if they meet a condition and at the same time wrap the original object as well if it meets the same condition? I already have the mapper creation abstracted away so I can mold the original object automatically before passing it to the mapper, which may help with subjecting the original object to the resolver as well by doing mapper.Map(new { Root = originalObject }) so the resolver sees the instance of the original object as if it was a value of a property of source object as well, not the source object itself.
According to this issue on AutoMapper GitHub page, there is no direct way to do it.
But there is some workarounds. For example - reflection.
In this case you need to know wrapper type and implement converter for desired types. In this example it's MapAndWrapConverter from TSource to Wrap<TDestination>
CreateWrapMap method creates two bindings:
SourceAddress -> Wrap<DestinationAddress> and SourceContact -> Wrap<DestinationContact> which allow you to map SourceContant to wrapped DestinationContact.
internal class Program
{
public static void Main()
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<SourceAddress, DestinationAddress>();
cfg.CreateMap<SourceContact, DestinationContact>();
cfg.CreateWrapMap(
//func selecting types to wrap
type => typeof(DestinationModelBase).IsAssignableFrom(type)
&& !type.IsAbstract,
typeof(Wrap<>),
typeof(MapAndWrapConverter<,>));
});
var mapper = config.CreateMapper();
//Using AutoFixture to create complex object
var fixture = new Fixture();
var srcObj = fixture.Create<SourceContact>();
var dstObj = mapper.Map<Wrap<DestinationContact>>(srcObj);
}
}
public static class AutoMapperEx
{
public static IMapperConfigurationExpression CreateWrapMap(
this IMapperConfigurationExpression cfg,
Func<Type, bool> needWrap, Type wrapperGenericType,
Type converterGenericType)
{
var mapperConfiguration =
new MapperConfiguration((MapperConfigurationExpression)cfg);
var types = Assembly.GetExecutingAssembly().GetTypes();
foreach (var dstType in types.Where(needWrap))
{
var srcType = mapperConfiguration.GetAllTypeMaps()
.Single(map => map.DestinationType == dstType).SourceType;
var wrapperDstType = wrapperGenericType.MakeGenericType(dstType);
var converterType = converterGenericType.MakeGenericType(srcType, dstType);
cfg.CreateMap(srcType, wrapperDstType)
.ConvertUsing(converterType);
}
return cfg;
}
}
public class MapAndWrapConverter<TSource, TDestination>
: ITypeConverter<TSource, Wrap<TDestination>>
{
public Wrap<TDestination> Convert(
TSource source, Wrap<TDestination> destination, ResolutionContext context)
{
return new Wrap<TDestination>
{
Payload = context.Mapper.Map<TDestination>(source)
};
}
}
CreateWrapMap method is a little bit messy, especially the part with finding matching types. But it can be refined according to your needs.

Indexing a derived [ElasticType] only serializes the base class' properties

For the life of me, I cannot figure out why Nest is only serializing the below base class' properties when indexing an instance, even though I am telling it to index the derived class.
The base class:
[ElasticType(Name = "activity")]
public class Activity
{
[ElasticProperty(Name = "name")]
public string Name { get; set; }
[ElasticProperty(OptOut = true)]
public DateTimeOffset Timestamp
{
get { return DateTimeOffset.ParseExact(TimestampAsString, "yyyyMMddTHHmmssZ", CultureInfo.InvariantCulture).ToUniversalTime(); }
set { TimestampAsString = value.ToString("yyyyMMddTHHmmssZ"); }
}
[Obsolete("Use Timestamp, instead.")]
[ElasticProperty(Name = "timestamp")]
public string TimestampAsString { get; set; }
[ElasticProperty(Name = "application")]
public string Application { get; set; }
[ElasticProperty(Name = "application_version")]
public string ApplicationVersion { get; set; }
[ElasticProperty(OptOut = true)]
public IPAddress RemoteIpAddress
{
get { return IPAddress.Parse(RemoteIpAddressAsString); }
set { RemoteIpAddressAsString = value.ToString(); }
}
[Obsolete("Use RemoteIpAddress, instead.")]
[ElasticProperty(Name = "remote_ip_address")]
public string RemoteIpAddressAsString { get; set; }
}
The derived class:
private class SearchCountsRetrievedActivity : Activity
{
[ElasticProperty(OptOut = true)]
public PunctuationlessGuid? PrincipalIdentityId
{
set { PrincipalIdentityIdAsString = value; }
}
[ElasticProperty(Name = "principal_identity_id")]
public string PrincipalIdentityIdAsString { get; set; }
}
My index wrapper method:
public Task IndexActivityAsync<TActivity>(TActivity activity)
where TActivity : Activity
{
return _client.IndexAsync(activity);
}
No matter what I do, the serialized JSON sent over the wire only includes the Activity class' properties. What I've tried:
Making the derived classes public
Adding [ElasticType] to the derived classes
Inspecting the Nest source code (this is very difficult as the source code is very complex, and the NuGet package I'm referencing, even though it's the latest one, appears to be not forward-compatible with the latest source)
Apparently, the underlying default JSON serializer does not serialize properties with a null value. In my case, my property values were null, so that's why they weren't being serialized. The serializer works properly when values are not null.

Categories

Resources