views:

41

answers:

1

Hello,

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 !

A: 

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;
        }
    }
noon
The `\\{[\\n\\r ]*\"__type` pattern posted is dangerous, that would discriminate against any properties serialized prior to `__type` or pickup any property containing the text `"__type` as well as using implicit space rather then explicit `\s` for whitespace and platform specific newline sequence.
Quintin Robinson
The __type is like a keyword for the DataContractJsonSerializer. It must be placed prior any other fields (and actually prior to any other characters) otherwise the json doesn't get serialized in the correct type. Concerning plateform specific characters I'll changed that. Thanks.
noon