I want to design an API that accept some generic contract and also can store additional values that extends generic schema. It looks like IExtensibleDataObject
machinery was built for similar task and it can be useful for my usecase.
But, when I try to use it, I found strange behaviour of the ExtensionDataObject
property deserialization in case of usage of EmitDefaultValue = false
option for DataMember
.
Consider following data contracts (note, that fields of the Item
DTO has EmitDefaultValue = false
option):
[DataContract(Name = "Contract", Namespace = "")]
public class ContractGeneric : IExtensibleDataObject
{
[DataMember(Name = "id", Order = 1)]
public string Id;
public ExtensionDataObject? ExtensionData { get; set; }
}
[DataContract(Name = "Contract", Namespace = "")]
public class ContractExtended
{
[DataMember(Name = "id", Order = 1)]
public string Id;
[DataMember(Name = "items", Order = 2)]
public Item[] Items;
}
[DataContract(Name = "Item", Namespace = "")]
public class Item
{
[DataMember(Name = "code", Order = 1, EmitDefaultValue = false)]
public string Code;
[DataMember(Name = "name", Order = 2, EmitDefaultValue = false)]
public string Name;
}
The definition of contracts looks fine for me, but serialization of ContractGeneric
DTO parsed from some ContractExtended
DTO with DataContractJsonSerializer
can crash with a strange exception:
Unhandled exception. System.Runtime.Serialization.SerializationException: There was an error serializing the object of type TestSerialization.ContractGeneric. Encountered unexpected el
ement local name 'code' for item in collection. 'item' is the only valid local name for elements in a collection.
For example, you can get a crash situation with the following example:
public class Program
{
public static void Main()
{
var v2 = new ContractExtended {Id = "1", Items = new[] {new Item {Code = "0"}}};
var v2Json = ToJson(v2);
Console.WriteLine(v2Json);
var v1 = FromJson<ContractGeneric>(v2Json);
var v1Json = ToJson(v1);
Console.WriteLine(v1Json);
}
public static string ToJson<T>(T value)
{
using var memoryStream = new MemoryStream();
new DataContractJsonSerializer(typeof(T)).WriteObject(memoryStream, value);
return Encoding.UTF8.GetString(memoryStream.ToArray());
}
public static T FromJson<T>(string json)
{
using var memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(json));
return (T) new DataContractJsonSerializer(typeof(T)).ReadObject(memoryStream);
}
}
It looks like there is a bug in the deserialization process and internally ExtendedObject
think that one element of items
collection also is a collection, but this is wrong and serializator detect this discrepancy only at the time of serialization process.
Can I avoid such strange exception while preserving EmitDefaultValue
option and also stay in the JSON
world (because it seems like XML serialization handles this situations correctly). Or this is a bug or some kind of limitation of DataContractJsonSerializer
?