1

I have created a dictionary with polymorphic values in which I have saved a class object. I have successfully serialized the JSON. But I am unable to deserialize it. It gives below error:

Element ':Value' contains data of the ':Sale' data contract. The deserializer has no knowledge of any type that maps to this contract.

If replace the JSON property "__type" with "type" then it works but is unable to recover correct object type. Before serialization it contains an object of my class type but after deserialization it contains a system.object instead.

My code is below:

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;

class Program
{
    static void Main(string[] args)
    {
        Dictionary<string, object> dict = new Dictionary<string, object>();
        dict.Add("employee","john");
        dict.Add("sale",new Sale(9,5243));
        dict.Add("restaurant",new Restaurant("Cheese Cake Factory", "New York"));
        //  Console.Write(dict["sale"]);

        //Code for JSON
        DataContractJsonSerializer js = new DataContractJsonSerializer(typeof(Dictionary<string, object>));  
        MemoryStream msObj = new MemoryStream();  
        js.WriteObject(msObj, dict);  
        msObj.Position = 0;  
        StreamReader sr = new StreamReader(msObj);  
        string json = sr.ReadToEnd();  
        sr.Close();  
        msObj.Close();

        // Decode the thing
        Console.Write(json);

        Dictionary<string, object> result = new Dictionary<string, object>();
        using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(json)))
        {
            DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(Dictionary<string, object>));
            result = serializer.ReadObject(stream) as Dictionary<string, object>;
        }
    }
}

[DataContract]
[KnownType(typeof(Sale))]
public class Sale 
{
    [DataMember(Name = "SaleId")]
    public int SaleId {get; set;}
    [DataMember(Name = "Total")]
    public int Total{ get; set;}

    public Sale(int saleid, int total)
    {
        SaleId = saleid;
        Total = total;
    }

    public int getTotal()
    {
        return  Total;
    }
}

[DataContract(Name = "Restaurant", Namespace="")]
[KnownType(typeof(Restaurant))]
public class Restaurant
{
    [DataMember(EmitDefaultValue = false)]
    public string Name {get; set;}
    [DataMember(EmitDefaultValue = false)]
    public string City{get; set;}

    public Restaurant(string name, string city)
    {
        Name = name;
        City = city;
    }
}

Fiddle link : https://dotnetfiddle.net/CfQxxV

dbc
  • 104,963
  • 20
  • 228
  • 340

2 Answers2

1

The problem you're having is that you're putting [KnownType(typeof(...))] above sale and Restaurant.

The reason for using KnownType is for conversion between 1 and the other object. So the deserializer doesn't know that Sale is a KnownType of Object. so it can't Convert Object to Sale.

This would only work if all the items in your dictionary share a common parent object like this:

    [KnownType(typeof(Sale))]
    [KnownType(typeof(Restaurant))]
    [KnownType(typeof(Employee))]
    [DataContract]
    public class SomeObject {

    }

    [DataContract(Name = "Sale", Namespace="")]
    public class Sale : SomeObject
    {
       //methods + properties + variables
    }

    [DataContract(Name = "Restaurant", Namespace="")]
    public class Restaurant : SomeObject
    {
        //methods + properties + variables   
    }

    [DataContract(Name = "Employee", Namespace="")]
    public class Employee: SomeObject
    {
        //methods + properties + variables   
    }

and then use the dictionary as

Dictionary<string, SomeObject> dict = new Dictionary<string, SomeObject>();
Joost K
  • 1,096
  • 8
  • 23
  • Thanks for quick solution. It'working fine. I have updated the fiddle. But I unable to add non object. So have you any idea? so that I can also add below. dict.Add("employee","john"); – Rakesh Kumar Nov 13 '19 at 12:22
  • It would also need to be an object that extends `SomeObject`, I slightly edited my answer – Joost K Nov 13 '19 at 13:04
1

You are attempting to serialize a root object Dictionary<string, object> with polymorphic members. The data contract serializers use a whitelisting approach to polymorphism: all polymorphic subtypes encountered during serialization must have been previously declared via the known type mechanism before an instance of that subtype is encountered in the serialization graph.

So, how can that be done using your data model? There are several approaches:

  1. Add [KnownType(typeof(TDerivedObject))] directly to a base type that is statically declared as shown in this answer by Joost K.

    This can't work here because the base type is object, which you cannot modify.

  2. Add [KnownType(typeof(TDerivedObject))] to some parent object in the serialization graph.

    This looks problematic because your root object type is Dictionary<string, object>, however you could subclass the dictionary to get your desired result:

    [KnownType(typeof(Sale))]
    [KnownType(typeof(Restaurant))]
    public class ObjectDictionary : Dictionary<string, object>
    {
    }
    

    Then construct and serialize your dictionary using this subclass:

    var dict = new ObjectDictionary()
    {
        { "employee","john" },
        {"sale",new Sale(9,5243) },
        {"restaurant",new Restaurant("Cheese Cake Factory", "New York")},
    };
    
    DataContractJsonSerializer js = new DataContractJsonSerializer(typeof(ObjectDictionary));  
    

    Demo fiddle #1 here.

  3. Configure additional known types in runtime by constructing a serializer using a DataContractJsonSerializerSettings with known types specified in DataContractJsonSerializerSettings.KnownTypes:

    var settings = new DataContractJsonSerializerSettings
    {
        KnownTypes = new [] { typeof(Sale), typeof(Restaurant) },
    };
    DataContractJsonSerializer js = new DataContractJsonSerializer(typeof(Dictionary<string, object>), settings);  
    

    Be sure to configure the serializer in the same way for both serialization and deserialization.

    Demo fiddle #2 here.

    (For XML use DataContractSerializerSettings.KnownTypes.)

  4. Specify additional known types via configuration file as shown in Additional Ways to Add Known Types.

  5. You are serializing directly, but if you were serializing via WCF you could add ServiceKnownTypeAttribute to your service contract.

It is never necessary to add [KnownType(typeof(TClass))] to TClass itself, so you can remove such attributes from Restaurant and Sale:

[DataContract]
//[KnownType(typeof(Sale))] Remove this
public class Sale 
{
    // Remainder unchanged
}

For more, see All About KnownTypes by Sowmy Srinivasan.

dbc
  • 104,963
  • 20
  • 228
  • 340