Why json serializer is not compliant with polymorphism? - c#

I use stock JSON serialiser in .NET 4.5 windows store app -
System.Runtime.Serialization.Json.DataContractJsonSerializer
I have a class which is supplied by the API provider say
class A { public DateTime Date {get;set} }
I wanted to hide Date field by doing this (NOTE - new keyword):
class AEx : A { public new string Date {get;set} }
But I am getting exception:
type 'AEx' is not serializable with DataContractJsonSerializer because the data member 'Date' is duplicated in its type hierarchy.
I understand what it does.. My question however is not HOW does it do but why?
Logically speaking it should be compatible with the programming features like polymorphism. Why this class was made to ignore it?

Your class basically contains two properties with the same name. So if you try to deserialize some JSON which contains that property name, there's no way of knowing which property to set.
Likewise when serializing an object of that type, you've got two values which need to be serialized using the same property name - what would you expect the JSON to look like?
Fundamentally, a type with two properties of the same name is incompatible with serialization of a plain name/value pair format. (A more advanced serialization mechanism which could specify which property was associated with which declaring class would cope, but JSON doesn't do that.)

Related

Serialize class properties with their types automatically

My boss have a strange request, he wants me to add a new function to serialize and deserialize all our products classes and add in the XML file all their property's types automatically.
I can't modify the classes to add new "types properties" before every "real property".
Is there a way to do this with [XmlAttributes] or something else ?
Thank you.
Maybe this is what you are looking for https://learn.microsoft.com/dotnet/api/system.xml.serialization.xmltypeattribute?view=netcore-3.1
this article describes the XmlTypeAttribute, which you can use to add a type attribute (in xml) on your properties tag.
[XmlType("aType")]
public string MyProperty {get;set;}

Converting type of a property of a DynamicObject based on ReturnType

I have a dynamic type in C# (Content, a type that inherits DynamicObject). It wraps an inner JSON object containing the data. In TryGetMember I return the requested properties from this inner JSON object. This works just fine in case of simple types (e.g. an int or string property), because .Net converts those values correctly.
But I want to use properties with more complex types:
dynamic content = Content.Load(id);
IEnumerable<Book> bookList = content.Books;
The problem is that in TryGetMember of my class I have no way to know the type that I should convert to (in this case the IEnumerable Book), because binder.ReturnType is always Object. According to this article, this is the normal behavior:
Determining the expected type of a DynamicObject member access
But I find this very hard to believe: how is it possible that the API does not let me know the target type? This way I will have to force developers to use the method syntax to specify the type explicitely:
IEnumerable<Books> bookList = content.Books<IEnumerable<Book>>();
...which is ugly and weird.
You could store Type data alongside the JSON serialized data, but that seems like a rather inefficient method of accomplishing what you're trying to do. If your content isn't truely dynamic (e.g., the content changes, but the basic schema of the object is the same), you could just have precompiled classes for each JSON object type, and serialize the JSON into that class once you receive the data. This way, all of the type data would already be recognized by the compiler at runtime.
It turns out that this is not possible indeed. I ended up creating an extension method (defined in two forms: for dynamic collections and JArrays). That way I can at least get a collection of my Content class and all of the following solutions work:
One line, but a bit long
var members = ((IEnumerable<dynamic>)group.Members).ToContentEnumerable();
Separate lines
IEnumerable<dynamic> members = adminGroup.Members;
foreach (dynamic member in members.ToContentEnumerable())
{
//...
}
Using the method call syntax of the extension method (a bit unusual).
var members = ContentExtensions.ToContentEnumerable(group.Members);

Json.NET serialize List<System.Enum> and back to concrete types

I have a UserProfile C# class that contains a public list List<System.Enum> Bonuses;, whose elements each belong to one of several concrete types of Enums: BonusUI, BonusMusic, BonusMode, etc.
I'm using Json.NET and I wanted to serialize/deserialize the UserProfile object. I applied the [JsonConverter(typeof(StringEnumConverter))] to each concrete enum type, so that they are saved as strings instead of ints. However, this is not enough, as the saved enum values don't even contain their exact type.
...
"Bonuses": [
"MainTheme",
"ClassicUI",
"WoodUI",
"Rampage",
"SoleSurvivor"
],
...
For example, see this error when the deserializer finds BonusMusic.MainTheme (serialized as "MainTheme") inside the list:
Error converting value "MainTheme" to type 'System.Enum'.
How could I solve this?
The MainTheme as a simple string cannot be converted to an Enum. You should parse it using something like
BonusMusic enum = (BonusMusic)Enum.Parse(typeof(BonusMusic), "MainTheme");
Maybe you can implement a JSON custom serializer that could parse the values that come in the Bonuses array
You can check this JSON.NET Implementing Custom Serialization
Regards.

How to rename and remap fields with illegal json field names

I have used json2csharp to produce some nice c# class into which I can deserialize json.
It is actually working as expected EXCEPT that json2csharp named some of the fields invalid_name . I renamed those to valid csharp names but when serialized those class are null.
I found this other SO post... where one of the answerers said the following...
Keep in mind the class I have pasted below will not work directly,
because of the naming of some of the fields in the json. You may have
to rename them manually and map them.
This exactly describes my problem. Unfortunately, the answer gives no clues on actually HOW to "map them." So can someone tell me how to manually map some json arrays to c# classes.
I am using RestSharp deserializers, btw.
Any ideas?
If you are using JSON.NET as the basis or JSON parsing, you can rename your properties and then decorate them with attributes to align them back to the original JSON object.
An example is a property in JSON called 1 which is invalid in C#. This can be corrected using the following:
[JsonProperty("1")]
public int? One { get; set; }
There is also a JsonObject attribute if you want to edit at the class level, and also a JsonIgnore object to ignore serialisation of properties.

Convert data from object of class A to an object from class B

I have three different classes:
Task
Order
Transmission
Each class have properties with different types. Also, there is a possibility to attach data that represented by custom fields (implemented by an array of IField, where IField can be text field or list field). Each custom field have a name that represent the name of the attached data property.
I need to convert between each class to another:
Task -> Order
Order -> Transmission
Transmission -> Task
Transmission -> Order
Order -> Task
Task -> Transmission
for that I created:
Static class of static keys where each key represents the name of
the property.
"DataObject" that holds a dictionary of a property name and an object as its value.
Each class (Task, Order, Transmission) implements IDataShare interface:
public interface IDataShare
{
DataObject ToDataObject();
void FromDataObject(DataObject data);
}
For example, task objects with the following properties:
WorkerId:5
CustomerId:7
VehicleId:null
StartDate:null
And with the following custom fields:
Subcontractor: {listId:5, Value:4} (this is list field)
delivery Note: "abc" (this is text field)
will be convert to the following dictionary:
{"WorkerId", 5}
{"CustomerId", 7}
{"VehicleId", null}
{"StartDate", null}
{"Subcontractor", {listId:5, Value:4}}
{"delivery Note", "abc"}
the string keys "WorkerId", "CustomerId", "VehicleId", "StartDate" were taken from static class that contains string consts where "Subcontractor" and "deliveryNote" are the names of the custom fields the user added (I don't know which fields the user might add so I just use the field name).
When I fill an object using DataObject I have to verify the name of the property is the same as the name of the key and also verify the value is correct (string cannot inserted into long).
In addition, custom list field (subcontractor) can't have only itemId as a value because when I have to verify that the listId of the custom field in the object is the same with the listId of the customField in the DataObject.
I have many problems about knowing the type of the value. I always have to use "X is Y" if statements of "X as Y" statements. In addition, I have to remember how to store the type of the value when implementing IDataShare interface which makes the work harder.
Can anyone help me think about constraint I can add to the conversion proccess from an object to DataObject? Can anyone help me improve this method of converting objects?
Thanks!
UPDATE
I want to explain a point. My biggest problem is that there are several ways to translate each property/custom field so I need to remember the type of the value in DataObject. For example, in Transmission class I have VehicleId property. I want to convert Task object with custom field with the name "VehicleId" to Transmission. All I want is that Task's custom field VehicleId's value will be converted into the VehicleId property of Transmission. But, because it is custom field - as I wrote before - there is a way I store custom field that based on a list: {listId:5, Value:4}. Now, in the conversion proccess (FromDataObject in Transmission) in case the DataObject has "VehicleId" key, I have to check whether the value is long (vehicle id as property) or IListField (vehicle id as custom list field).
those type checking really makes mess.
Well, if the number of classes you're converting between is really as limited as you've said, may I suggest just writing casting operators for your classes?
http://msdn.microsoft.com/en-us/library/xhbhezf4%28v=VS.100%29.aspx
It seems like the amount of logic that you're putting into the conversion is enough to warrant something like this.
On the other hand, it seems like there is a base set of fields being used across the different objects and you're just stuffing them into an untyped dictionary. If the fields are common across all types, could you use a conversion to a strongly typed common object?
Which also begs the question: could you use a common base class object?
If you have options of modifying the Task, Order, and Transmission definitions, I'd take a look at them again. This sort of scenario seems like a "code smell".
If I understand this correctly ToDataObjectis basically a serializer and FromDataObject is a deserializer. If the data contained by these object is type compatible, then it seems that the very act of serializing it into untyped data is the source of your problem. Why do this, instead of just keeping the data in its native format?
If you need to use an adapter because there are incompatibilities between the objects that can't be resolved for some reason, I would think that you can make one that at least keep the data in its native structures instead of serializing everything to a string. A dictionary in C# can contain anything, at a minimum you could be using a Dictionary<string,object>.
It's also unclear what all this verification is about - why would data be incompatible, if you are mapping properties of the same data types? E.g. assuming that this is an internal process, under what circumstance could (e.g.) a string from one object be trying to be assigned to a long in another object? Seems that would only be necessary if the data were strongly typed in one object, but not in another.
Have you considered using generics?
If Task, Order and Transmission all inherit from a base class like Property, then you could expose a common method for getting the values you need.
GetMyValue() where T : Property
It's not very clear what you are trying to achieve.

Categories