Does IExtensibleDataObject work with EmitDefaultValue option and DataContractJsonSerializer?

150 Views Asked by At

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?

0

There are 0 best solutions below