Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JSON deseralization to abstract list using DataContractJsonSerializer

I'm trying to deserialize a JSon file to an instance of a class that contains an abstract list. Serializing the instance to the Json works well (check the json file below). When deserializing I get a "System.MemberAccessException" with the message "Cannot create an abstract class". Obvisouly the deseralizer is trying to instantiate the abstract class and not the concrete class.

In my example the deserialized class is called ElementContainer :

namespace Data
{
    [DataContract]
    [KnownType(typeof(ElementA))]
    [KnownType(typeof(ElementB))]
    public class ElementContainer
    {
        [DataMember]
        public List<Element> Elements { get; set; }
    }

    [DataContract]
    public abstract class Element
    {
    }

    [DataContract]
    public class ElementA : Element
    {
        [DataMember]
        int Id { get; set; }
    }

    [DataContract]
    public class ElementB : Element
    {
        [DataMember]
        string Name { get; set; }
    }
}

This is the Json file that was serialized and that I'm trying to deserialize. Notice the "__type" field for the deserializer to create the concrete classes :

{
    "Elements":
    [
        {
            "__type":"ElementA:#Data",
            "Id":1
        }, 
        {
            "__type":"ElementB:#Data",
            "Name":"MyName"
        }       
    ]
}

The following is the code I'm using for deserialization :

    public T LoadFromJSON<T>(string filePath)
    {
        try
        {
            using (FileStream stream = File.OpenRead(filePath))
            {
                DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(T));
                T contract = (T)serializer.ReadObject(stream);
                return contract;
            }
        }
        catch (System.Exception ex)
        {
            logger.Error("Cannot deserialize json " + filePath, ex);
            throw;
        }
    }

It is possible to make the deserialization work ?

Thanks !

like image 433
noon Avatar asked Oct 19 '10 18:10

noon


1 Answers

We've found why it wasn't working. Just after the serialization of the object we ident the resulting string for more readability. Then we write the string into a file :

    public void SaveContractToJSON<T>(T contract, string filePath)
    {
        using (MemoryStream stream = new MemoryStream())
        {
            DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(T));
            serializer.WriteObject(stream, contract);
            string json = Encoding.UTF8.GetString(stream.ToArray());
            File.WriteAllText(filePath, json.IndentJSON());
        }
    }

The identation is actually the reason why the deserialization was not working. It seems the parser of the DataContractJsonSerializer is really picky. If some characters are between the character { and the field "__type", the serializer get lost.

For example this string will serialize correctly :

"{\"Elements\":[{\"__type\":\"ElementA:#Data\",\"Id\":1}]}"

But this next string will not serialize.

"{\"Elements\":[   {\"__type\":\"ElementA:#Data\",\"Id\":1}]}"

The only difference is the space characters before the "__type". The serialization will throw a MemberAccessException. This is misleading because this behavior appears only when deserializing into an abstract List. Serializing into an abstract field works fine no matter the characters.

To fix this issue without removing the readability of the file, The string can be modified before the deseralization. For example :

    public T LoadContractFromJSON<T>(string filePath)
    {
        try
        {
            string text = File.ReadAllText(filePath);
            text  = Regex.Replace(text, "\\{[\\n\\r ]*\"__type", "{\"__type");
            using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(text)))
            {
                DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(T));
                T contract = (T)serializer.ReadObject(stream);
                return contract;
            }
        }
        catch (System.Exception ex)
        {
            logger.Error("Cannot deserialize json " + filePath, ex);
            throw;
        }
    }
like image 74
noon Avatar answered Oct 05 '22 22:10

noon