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();
Related
I have a class MyClass. I would like to convert this to a dynamic object so I can add a property.
This is what I had hoped for:
dynamic dto = Factory.Create(id);
dto.newProperty = "123";
I get the error:
WEB.Models.MyClass does not contain a definition for 'newProperty'
Is that not possible?
The following has worked for me in the past:
It allows you to convert any object to an Expando object.
public static dynamic ToDynamic<T>(this T obj)
{
IDictionary<string, object> expando = new ExpandoObject();
foreach (var propertyInfo in typeof(T).GetProperties())
{
var currentValue = propertyInfo.GetValue(obj);
expando.Add(propertyInfo.Name, currentValue);
}
return expando as ExpandoObject;
}
Based on: http://geekswithblogs.net/Nettuce/archive/2012/06/02/convert-dynamic-to-type-and-convert-type-to-dynamic.aspx
As my object has JSON specific naming, I came up with this as an alternative:
public static dynamic ToDynamic(this object obj)
{
var json = JsonConvert.SerializeObject(obj);
return JsonConvert.DeserializeObject(json, typeof(ExpandoObject));
}
For me the results worked great:
Model:
public partial class Settings
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("runTime")]
public TimeSpan RunTime { get; set; }
[JsonProperty("retryInterval")]
public TimeSpan RetryInterval { get; set; }
[JsonProperty("retryCutoffTime")]
public TimeSpan RetryCutoffTime { get; set; }
[JsonProperty("cjisUrl")]
public string CjisUrl { get; set; }
[JsonProperty("cjisUserName")]
public string CjisUserName { get; set; }
[JsonIgnore]
public string CjisPassword { get; set; }
[JsonProperty("importDirectory")]
public string ImportDirectory { get; set; }
[JsonProperty("exportDirectory")]
public string ExportDirectory { get; set; }
[JsonProperty("exportFilename")]
public string ExportFilename { get; set; }
[JsonProperty("jMShareDirectory")]
public string JMShareDirectory { get; set; }
[JsonIgnore]
public string Database { get; set; }
}
I used it in this manner:
private static dynamic DynamicSettings(Settings settings)
{
var settingsDyn = settings.ToDynamic();
if (settingsDyn == null)
return settings;
settingsDyn.guid = Guid.NewGuid();
return settingsDyn;
}
And received this as a result:
{
"id": 1,
"runTime": "07:00:00",
"retryInterval": "00:05:00",
"retryCutoffTime": "09:00:00",
"cjisUrl": "xxxxxx",
"cjisUserName": "xxxxx",
"importDirectory": "import",
"exportDirectory": "output",
"exportFilename": "xxxx.xml",
"jMShareDirectory": "xxxxxxxx",
"guid": "210d936e-4b93-43dc-9866-4bbad4abd7e7"
}
I don't know about speed, I mean it is serializing and deserializing, but for my use it has been great. A lot of flexability like hiding properties with JsonIgnore.
Note: xxxxx above is redaction. :)
You cannot add members to class instances on the fly.
But you can use ExpandoObject. Use factory to create new one and initialize it with properties which you have in MyClass:
public static ExpandoObject Create(int id)
{
dynamic obj = new ExpandoObject();
obj.Id = id;
obj.CreatedAt = DateTime.Now;
// etc
return obj;
}
Then you can add new members:
dynamic dto = Factory.Create(id);
dto.newProperty = "123";
You can't add properties to types at runtime. However, there is an exception which is: ExpandoObject. So if you need to add properties at runtime you should use ExpandoObject, other types don't support this.
Just to add up my experience, if you are using JSON.NET, then below might be one of the solution:
var obj....//let obj any object
ExpandoObject expandoObject= JsonConvert.DeserializeObject<ExpandoObject>(JsonConvert.SerializeObject(obj));
Not tested performances etc.. but works.
Is there a way to change name of Data property during serialization, so I can reuse this class in my WEB Api.
For an example, if i am returning paged list of users, Data property should be serialized as "users", if i'm returning list of items, should be called "items", etc.
Is something like this possible:
public class PagedData
{
[JsonProperty(PropertyName = "Set from constructor")]??
public IEnumerable<T> Data { get; private set; }
public int Count { get; private set; }
public int CurrentPage { get; private set; }
public int Offset { get; private set; }
public int RowsPerPage { get; private set; }
public int? PreviousPage { get; private set; }
public int? NextPage { get; private set; }
}
EDIT:
I would like to have a control over this functionality, such as passing name to be used if possible. If my class is called UserDTO, I still want serialized property to be called Users, not UserDTOs.
Example
var usersPagedData = new PagedData("Users", params...);
You can do this with a custom ContractResolver. The resolver can look for a custom attribute which will signal that you want the name of the JSON property to be based on the class of the items in the enumerable. If the item class has another attribute on it specifying its plural name, that name will then be used for the enumerable property, otherwise the item class name itself will be pluralized and used as the enumerable property name. Below is the code you would need.
First let's define some custom attributes:
public class JsonPropertyNameBasedOnItemClassAttribute : Attribute
{
}
public class JsonPluralNameAttribute : Attribute
{
public string PluralName { get; set; }
public JsonPluralNameAttribute(string pluralName)
{
PluralName = pluralName;
}
}
And then the resolver:
public class CustomResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty prop = base.CreateProperty(member, memberSerialization);
if (prop.PropertyType.IsGenericType && member.GetCustomAttribute<JsonPropertyNameBasedOnItemClassAttribute>() != null)
{
Type itemType = prop.PropertyType.GetGenericArguments().First();
JsonPluralNameAttribute att = itemType.GetCustomAttribute<JsonPluralNameAttribute>();
prop.PropertyName = att != null ? att.PluralName : Pluralize(itemType.Name);
}
return prop;
}
protected string Pluralize(string name)
{
if (name.EndsWith("y") && !name.EndsWith("ay") && !name.EndsWith("ey") && !name.EndsWith("oy") && !name.EndsWith("uy"))
return name.Substring(0, name.Length - 1) + "ies";
if (name.EndsWith("s"))
return name + "es";
return name + "s";
}
}
Now you can decorate the variably-named property in your PagedData<T> class with the [JsonPropertyNameBasedOnItemClass] attribute:
public class PagedData<T>
{
[JsonPropertyNameBasedOnItemClass]
public IEnumerable<T> Data { get; private set; }
...
}
And decorate your DTO classes with the [JsonPluralName] attribute:
[JsonPluralName("Users")]
public class UserDTO
{
...
}
[JsonPluralName("Items")]
public class ItemDTO
{
...
}
Finally, to serialize, create an instance of JsonSerializerSettings, set the ContractResolver property, and pass the settings to JsonConvert.SerializeObject like so:
JsonSerializerSettings settings = new JsonSerializerSettings
{
ContractResolver = new CustomResolver()
};
string json = JsonConvert.SerializeObject(pagedData, settings);
Fiddle: https://dotnetfiddle.net/GqKBnx
If you're using Web API (looks like you are), then you can install the custom resolver into the pipeline via the Register method of the WebApiConfig class (in the App_Start folder).
JsonSerializerSettings settings = config.Formatters.JsonFormatter.SerializerSettings;
settings.ContractResolver = new CustomResolver();
Another Approach
Another possible approach uses a custom JsonConverter to handle the serialization of the PagedData class specifically instead using the more general "resolver + attributes" approach presented above. The converter approach requires that there be another property on your PagedData class which specifies the JSON name to use for the enumerable Data property. You could either pass this name in the PagedData constructor or set it separately, as long as you do it before serialization time. The converter will look for that name and use it when writing out JSON for the enumerable property.
Here is the code for the converter:
public class PagedDataConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(PagedData<>);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
Type type = value.GetType();
var bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
string dataPropertyName = (string)type.GetProperty("DataPropertyName", bindingFlags).GetValue(value);
if (string.IsNullOrEmpty(dataPropertyName))
{
dataPropertyName = "Data";
}
JObject jo = new JObject();
jo.Add(dataPropertyName, JArray.FromObject(type.GetProperty("Data").GetValue(value)));
foreach (PropertyInfo prop in type.GetProperties().Where(p => !p.Name.StartsWith("Data")))
{
jo.Add(prop.Name, new JValue(prop.GetValue(value)));
}
jo.WriteTo(writer);
}
public override bool CanRead
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
To use this converter, first add a string property called DataPropertyName to your PagedData class (it can be private if you like), then add a [JsonConverter] attribute to the class to tie it to the converter:
[JsonConverter(typeof(PagedDataConverter))]
public class PagedData<T>
{
private string DataPropertyName { get; set; }
public IEnumerable<T> Data { get; private set; }
...
}
And that's it. As long as you've set the DataPropertyName property, it will be picked up by the converter on serialization.
Fiddle: https://dotnetfiddle.net/8E8fEE
UPD Sep 2020: #RyanHarlich pointed that proposed solution doesn't work out of the box. I found that Newtonsoft.Json doesn't initialize getter-only properties in newer versions, but I'm pretty sure it did ATM I wrote this answer in 2016 (no proofs, sorry :).
A quick-n-dirty solution is to add public setters to all properties ( example in dotnetfiddle ). I encourage you to find a better solution that keeps read-only interface for data objects. I haven't used .Net for 3 years, so cannot give you that solution myself, sorry :/
Another option with no need to play with json formatters or use string replacements - only inheritance and overriding (still not very nice solution, imo):
public class MyUser { }
public class MyItem { }
// you cannot use it out of the box, because it's abstract,
// i.e. only for what's intended [=implemented].
public abstract class PaginatedData<T>
{
// abstract, so you don't forget to override it in ancestors
public abstract IEnumerable<T> Data { get; }
public int Count { get; }
public int CurrentPage { get; }
public int Offset { get; }
public int RowsPerPage { get; }
public int? PreviousPage { get; }
public int? NextPage { get; }
}
// you specify class explicitly
// name is clear,.. still not clearer than PaginatedData<MyUser> though
public sealed class PaginatedUsers : PaginatedData<MyUser>
{
// explicit mapping - more agile than implicit name convension
[JsonProperty("Users")]
public override IEnumerable<MyUser> Data { get; }
}
public sealed class PaginatedItems : PaginatedData<MyItem>
{
[JsonProperty("Items")]
public override IEnumerable<MyItem> Data { get; }
}
Here is a solution that doesn't require any change in the way you use the Json serializer. In fact, it should also work with other serializers. It uses the cool DynamicObject class.
The usage is just like you wanted:
var usersPagedData = new PagedData<User>("Users");
....
public class PagedData<T> : DynamicObject
{
private string _name;
public PagedData(string name)
{
if (name == null)
throw new ArgumentNullException(nameof(name));
_name = name;
}
public IEnumerable<T> Data { get; private set; }
public int Count { get; private set; }
public int CurrentPage { get; private set; }
public int Offset { get; private set; }
public int RowsPerPage { get; private set; }
public int? PreviousPage { get; private set; }
public int? NextPage { get; private set; }
public override IEnumerable<string> GetDynamicMemberNames()
{
yield return _name;
foreach (var prop in GetType().GetProperties().Where(p => p.CanRead && p.GetIndexParameters().Length == 0 && p.Name != nameof(Data)))
{
yield return prop.Name;
}
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
if (binder.Name == _name)
{
result = Data;
return true;
}
return base.TryGetMember(binder, out result);
}
}
The following is another solution tested in .NET Standard 2.
public class PagedResult<T> where T : class
{
[JsonPropertyNameBasedOnItemClassAttribute]
public List<T> Results { get; set; }
[JsonProperty("count")]
public long Count { get; set; }
[JsonProperty("total_count")]
public long TotalCount { get; set; }
[JsonProperty("current_page")]
public long CurrentPage { get; set; }
[JsonProperty("per_page")]
public long PerPage { get; set; }
[JsonProperty("pages")]
public long Pages { get; set; }
}
I am using Humanizer for pluralization.
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
if (member.GetCustomAttribute<JsonPropertyNameBasedOnItemClassAttribute>() != null)
{
Type[] arguments = property.DeclaringType.GenericTypeArguments;
if(arguments.Length > 0)
{
string name = arguments[0].Name.ToString();
property.PropertyName = name.ToLower().Pluralize();
}
return property;
}
return base.CreateProperty(member, memberSerialization);
}
There's a package called SerializationInterceptor. Here's the GitHub link: https://github.com/Dorin-Mocan/SerializationInterceptor/wiki. You can also install the package using Nuget Package Manager.
The example from below uses Syste.Text.Json for serialization. You can use any other serializer(except Newtonsoft.Json). For more info on why Newtonsoft.Json not allowed, please refer to GitHub documentation.
You can create an interceptor
public class JsonPropertyNameInterceptorAttribute : InterceptorAttribute
{
public JsonPropertyNameInterceptorAttribute(string interceptorId)
: base(interceptorId, typeof(JsonPropertyNameAttribute))
{
}
protected override void Intercept(in AttributeParams originalAttributeParams, object context)
{
string theNameYouWant;
switch (InterceptorId)
{
case "some id":
theNameYouWant = (string)context;
break;
default:
return;
}
originalAttributeParams.ConstructorArgs.First().ArgValue = theNameYouWant;
}
}
And put the interceptor on the Data prop
public class PagedData<T>
{
[JsonPropertyNameInterceptor("some id")]
[JsonPropertyName("during serialization this value will be replaced with the one passed in context")]
public IEnumerable<T> Data { get; private set; }
public int Count { get; private set; }
public int CurrentPage { get; private set; }
public int Offset { get; private set; }
public int RowsPerPage { get; private set; }
public int? PreviousPage { get; private set; }
public int? NextPage { get; private set; }
}
And then you can serialize the object like this
var serializedObj = InterceptSerialization(
obj,
objType,
(o, t) =>
{
return JsonSerializer.Serialize(o, t, new JsonSerializerOptions { ReferenceHandler = ReferenceHandler.Preserve });
},
context: "the name you want");
Hope this will be of use to you.
have a look here:
How to rename JSON key
Its not done during serialization but with a string operation.
Not very nice (in my eyes) but at least a possibility.
Cheers Thomas
I have a weird situation where I have objects and Lists of objects as part of my entities and contracts to interface with a third-party service. I'm going to try to see if I can replace the actual object class with something more specific in the entities and contracts to get around this, but I am curious if there is a way to get AutoMapper to handle this as is.
Here are some dummy classes:
public class From
{
public object Item { get; set; }
}
public class FromObject
{
public string Value { get; set; }
}
public class To
{
public object Item { get; set; }
}
public class ToObject
{
public string Value { get; set; }
}
And the quick replication:
Mapper.CreateMap<From, To>();
Mapper.CreateMap<FromObject, ToObject>();
From from = new From { Item = new FromObject { Value = "Test" } };
To to = Mapper.Map<To>(from);
string type = to.Item.GetType().Name; // FromObject
Basically, the question is this: Is there a way to get AutoMapper to understand that from.Item is a FromObject and apply the mapping to ToObject? I'm thinking there's probably not a way to make it automatic, since there's nothing that would indicate that to.Item has to be a ToObject, but is there a way to specify during the CreateMap or Map calls that this should be taken into account?
I don't think there is an "automatic" way of doing it, since AutoMapper won't be able to figure out that From.Item is FromObject and To.Item is ToObject.
But, while creating mapping, you can specify that
Mapper.CreateMap<FromObject, ToObject>();
Mapper.CreateMap<From, To>()
.ForMember(dest => dest.Item, opt => opt.MapFrom(src => Mapper.Map<ToObject>(src.Item)));
From from = new From { Item = new FromObject { Value = "Test" } };
To to = Mapper.Map<To>(from);
string type = to.Item.GetType().Name; // ToObject
If you're willing to use an additional interface, this can be accomplished using Include. You can't just map object to object in this fashion, though.
public class From
{
public IItem Item { get; set; }
}
public class FromObject : IItem
{
public string Value { get; set; }
}
public class To
{
public object Item { get; set; }
}
public class ToObject
{
public string Value { get; set; }
}
public interface IItem
{
// Nothing; just for grouping.
}
Mapper.CreateMap<From, To>();
Mapper.CreateMap<IItem, object>()
.Include<FromObject, ToObject>();
From from = new From { Item = new FromObject { Value = "Test" } };
To to = Mapper.Map<To>(from);
string type = to.Item.GetType().Name; // ToObject
I'm trying to use XMLSerializer to generate XML such as the following, where the contents of <create> is an array, but the elements can be of differing types (in this case <vendor>, <customer>, and <asset>). Is this possible?
...
<create>
<vendor>
<vendorid>Unit - A-1212</vendorid>
<name>this is the name8</name>
<vcf_bill_siteid3>FOOBAR8</vcf_bill_siteid3>
</vendor>
<customer>
<CUSTOMERID>XML121</CUSTOMERID>
<NAME>XML Customer 111</NAME>
</customer>
<asset>
<createdAt>San Jose</createdAt>
<createdBy>Kevin</createdBy>
<serial_number>123456789</serial_number>
</asset>
</create>
....
Assuming all possible types in the array are known at compile time, you can apply multiple [XmlArrayItem(String, Type)] attributes to the array for each known type that could appear in the array. The Type argument is a specific derived type that could appear in the array while the String argument is the element name to associate with that type. Also apply the [XmlArray(String)] attribute to the overall array property to specify the name of the array and that it is to be serialized in two levels rather than one.
E.g.:
public class Document
{
[XmlArray("create")]
[XmlArrayItem("vendor", typeof(Vendor))]
[XmlArrayItem("customer", typeof(Customer))]
[XmlArrayItem("asset", typeof(Asset))]
public CreateBase [] Create { get; set; }
}
Where
public abstract class CreateBase
{
}
public class Vendor : CreateBase
{
public string vendorid { get; set; }
public string name { get; set; }
public string vcf_bill_siteid3 { get; set; }
}
public class Customer : CreateBase
{
public string CUSTOMERID { get; set; }
public string NAME { get; set; }
}
public class Asset : CreateBase
{
public string createdAt { get; set; }
public string createdBy { get; set; }
public string serial_number { get; set; }
}
(Using an abstract base type is just my preference. You could use object as you base type: public object [] Create { get; set; })
Update
Serializing polymorphic collections containing derived types not known at compile time is difficult with XmlSerializer because it works through dynamic code generation. I.e. when you create an XmlSerializer for the first time it uses reflection to write c# code to serialize and deserialize all statically discoverable referenced types, then compiles and loads that code into a dynamic DLL to do the actual work. No code gets created for types that cannot be discovered statically, so (de)serialization will fail for them.
You have two options to work around this limitation:
Discover all derived types in the list at runtime, then construct an XmlAttributeOverrides, add XmlAttributes for the polymorphic array property, then fill in the XmlArrayItems for the array property with the discovered subtypes. Then pass the XmlAttributeOverrides to the appropriate XmlSerializer constructor.
Note - you must cache and reuse the XmlSerializer in an appropriate hash table or you will have an enormous resource leak. See here.
For an example of how this might be done, see here: Force XML serialization of XmlDefaultValue values.
Discover all derived types in the list at runtime, then store then in a custom List<T> subclass that implements IXmlSerializable.
Due to the nuisance of having to cache the XmlSerializer, I lean towards the second approach.
To discover all derived types:
public static class TypeExtensions
{
public static IEnumerable<Type> DerivedTypes(this IEnumerable<Type> baseTypes)
{
var assemblies = baseTypes.SelectMany(t => t.Assembly.GetReferencingAssembliesAndSelf()).Distinct();
return assemblies
.SelectMany(a => a.GetTypes())
.Where(t => baseTypes.Any(baseType => baseType.IsAssignableFrom(t)))
.Distinct();
}
}
public static class AssemblyExtensions
{
public static IEnumerable<Assembly> GetAllAssemblies()
{
// Adapted from
// https://stackoverflow.com/questions/851248/c-sharp-reflection-get-all-active-assemblies-in-a-solution
return Assembly.GetEntryAssembly().GetAllReferencedAssemblies();
}
public static IEnumerable<Assembly> GetAllReferencedAssemblies(this Assembly root)
{
// WARNING: Assembly.GetAllReferencedAssemblies() will optimize away any reference if there
// is not an explicit use of a type in that assembly from the referring assembly --
// And simply adding an attribute like [XmlInclude(typeof(T))] seems not to do
// the trick. See
// https://social.msdn.microsoft.com/Forums/vstudio/en-US/17f89058-5780-48c5-a43a-dbb4edab43ed/getreferencedassemblies-not-returning-complete-list?forum=netfxbcl
// Thus if you are using this to, say, discover all derived types of a base type, the assembly
// of the derived types MUST contain at least one type that is referenced explicitly from the
// root assembly, directly or indirectly.
var list = new HashSet<string>();
var stack = new Stack<Assembly>();
stack.Push(root);
do
{
var asm = stack.Pop();
yield return asm;
foreach (var reference in asm.GetReferencedAssemblies())
if (!list.Contains(reference.FullName))
{
stack.Push(Assembly.Load(reference));
list.Add(reference.FullName);
}
}
while (stack.Count > 0);
}
public static IEnumerable<Assembly> GetReferencingAssemblies(this Assembly target)
{
if (target == null)
throw new ArgumentNullException();
// Assemblies can have circular references:
// http://stackoverflow.com/questions/1316518/how-did-microsoft-create-assemblies-that-have-circular-references
// So a naive algorithm isn't going to work.
var done = new HashSet<Assembly>();
var root = Assembly.GetEntryAssembly();
var allAssemblies = root.GetAllReferencedAssemblies().ToList();
foreach (var assembly in GetAllAssemblies())
{
if (target == assembly)
continue;
if (done.Contains(assembly))
continue;
var refersTo = (assembly == root ? allAssemblies : assembly.GetAllReferencedAssemblies()).Contains(target);
done.Add(assembly);
if (refersTo)
yield return assembly;
}
}
public static IEnumerable<Assembly> GetReferencingAssembliesAndSelf(this Assembly target)
{
return new[] { target }.Concat(target.GetReferencingAssemblies());
}
}
Then, the polymorphic list that discovers its own types:
public class XmlPolymorphicList<T> : List<T>, IXmlSerializable where T : class
{
static XmlPolymorphicList()
{
// Make sure the scope of objects to find isn't *EVERYTHING*
if (typeof(T) == typeof(object))
{
throw new InvalidOperationException("Cannot create a XmlPolymorphicList<object>");
}
}
internal sealed class DerivedTypeDictionary
{
Dictionary<Type, string> derivedTypeNames;
Dictionary<string, Type> derivedTypes;
DerivedTypeDictionary()
{
derivedTypeNames = typeof(T).DerivedTypes().ToDictionary(t => t, t => t.DefaultXmlElementName());
derivedTypes = derivedTypeNames.ToDictionary(p => p.Value, p => p.Key); // Will throw an exception if names are not unique
}
public static DerivedTypeDictionary Instance { get { return Singleton<DerivedTypeDictionary>.Instance; } }
public string GetName(Type type)
{
return derivedTypeNames[type];
}
public Type GetType(string name)
{
return derivedTypes[name];
}
}
public XmlPolymorphicList()
: base()
{
}
public XmlPolymorphicList(IEnumerable<T> items)
: base(items)
{
}
#region IXmlSerializable Members
XmlSchema IXmlSerializable.GetSchema()
{
return null;
}
void IXmlSerializable.ReadXml(XmlReader reader)
{
reader.ReadStartElement();
while (reader.NodeType == XmlNodeType.Element)
{
var name = reader.Name;
var type = DerivedTypeDictionary.Instance.GetType(name);
var item = (T)(new XmlSerializer(type).Deserialize(reader));
if (item != null)
Add(item);
}
}
void IXmlSerializable.WriteXml(XmlWriter writer)
{
foreach (var item in this)
{
new XmlSerializer(item.GetType()).Serialize(writer, item);
}
}
#endregion
}
public static class XmlSerializationHelper
{
public static string DefaultXmlElementName(this Type type)
{
var xmlType = type.GetCustomAttribute<XmlTypeAttribute>();
if (xmlType != null && !string.IsNullOrEmpty(xmlType.TypeName))
return xmlType.TypeName;
return type.Name;
}
}
public class Singleton<T> where T : class
{
// Explicit static constructor to tell C# compiler
// not to mark type as beforefieldinit
static Singleton()
{
}
/// <summary>
/// Private nested class which acts as singleton class instantiator. This class should not be accessible outside <see cref="Singleton<T>"/>
/// </summary>
class Nested
{
/// <summary>
/// Explicit static constructor to tell C# compiler not to mark type as beforefieldinit
/// </summary>
static Nested()
{
}
/// <summary>
/// Static instance variable
/// </summary>
internal static readonly T instance = (T)Activator.CreateInstance(typeof(T), true);
}
public static T Instance { get { return Nested.instance; } }
}
In your c# class, just make sure you always return an empty array of any possible types that might be returned:
[Serializable]
public class create
{
public create()
{
vendor = new Vendor[0];
customer = new Customer[0];
asset = new Asset[0];
}
Vendor[] vendor { get; set; }
Customer[] customer { get; set; }
Asset[] asset { get; set; }
}
[Serializable]
public class Vendor
{
public string vendorid { get; set; }
public string name { get; set; }
public string vcf_bill_siteid3 { get; set; }
}
[Serializable]
public class Customer
{
public string CUSTOMERID { get; set; }
public string NAME { get; set; }
}
[Serializable]
public class Asset
{
public string createdAt { get; set; }
public string createdBy { get; set; }
public string serial_number { get; set; }
}
I ended up abandoning XmlSerializer and using Json.NET to serialize the object to json and then convert to XML.
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...