Bug 6721 - DataContractJsonSerializer cannot deserialize Dictionary, works if slightly reformatted
Summary: DataContractJsonSerializer cannot deserialize Dictionary, works if slightly r...
Status: NEW
Alias: None
Product: Class Libraries
Classification: Mono
Component: WCF assemblies (show other bugs)
Version: master
Hardware: PC Windows
: Normal normal
Target Milestone: Untriaged
Assignee: Bugzilla
URL:
Depends on:
Blocks:
 
Reported: 2012-08-28 08:15 UTC by steiner
Modified: 2013-05-22 17:19 UTC (History)
4 users (show)

See Also:
Tags:
Is this bug a regression?: ---
Last known good build:


Attachments

Description steiner 2012-08-28 08:15:01 UTC
I have data that I can deserialize using the following code, but which doesn't work on M4A.

First off let's start with the datacontract definition:

    [DataContract]
    public class CallStateChange
    {
        [DataMember]
        public string Line { get; set; }
        [DataMember]
        public List<CallInformation> NewCalls { get; set; }
        [DataMember]
        public List<long> EndedCalls { get; set; }
        [DataMember]
        public List<CallCapabilitiesChange> Changes { get; set; }
        [DataMember]
        public List<CallInformationUpdate> CallUpdates { get; set; }
        [DataMember]
        public long SequenceNumber { get; set; }

        public CallStateChange()
        {
            NewCalls = new List<CallInformation>();
            EndedCalls = new List<long>();
            Changes = new List<CallCapabilitiesChange>();
            CallUpdates = new List<CallInformationUpdate>();
        }

        public override string ToString()
        {
            StringBuilder sb = new StringBuilder("Call state changed for line ");
            sb.Append(Line);
            sb.Append(" sequence number ");
            sb.Append(SequenceNumber);
            sb.Append(Environment.NewLine);
            if (NewCalls != null)
            {
                sb.Append("New calls:");
                NewCalls.ForEach(c =>
                {
                    sb.Append(Environment.NewLine);
                    sb.Append("callref: ");
                    sb.Append(c.CallReference);
                    sb.Append(" caller ");
                    sb.Append(!string.IsNullOrEmpty(c.Name) ? c.Name + " (" + c.Number + ") " : (c.Number + " "));
                    if (!string.IsNullOrEmpty(c.LookedUpName))
                    {
                        sb.Append(" lookupname ");
                        sb.Append(c.LookedUpName);
                    }
                    if (!string.IsNullOrEmpty(c.E164Number))
                    {
                        sb.Append(" e164 number: ");
                        sb.Append(c.E164Number);
                    }
                    sb.Append(" state ");
                    sb.Append(c.CallState);
                });
            }
            if (EndedCalls != null)
            {
                sb.Append(Environment.NewLine);
                sb.Append("Ended calls: ");
                EndedCalls.ForEach(c => { sb.Append(c); sb.Append(" "); });
            }
            if (CallUpdates != null)
            {
                sb.Append(Environment.NewLine);
                sb.Append("Call updates: ");
                CallUpdates.ForEach(u =>
                {
                    sb.Append(Environment.NewLine);
                    sb.Append("Callref: ");
                    sb.Append(u.CallReference);
                    sb.Append(" ");
                    if (u.ChangedInformation != null)
                    {
                        foreach (string key in u.ChangedInformation.Keys)
                        {
                            sb.Append(key);
                            sb.Append("=");
                            sb.Append(u.ChangedInformation[key]);
                            sb.Append(" ");
                        }
                    }
                });
            }
            if (Changes != null)
            {
                sb.Append(Environment.NewLine);
                sb.Append("Capability updates: ");
                Changes.ForEach(c =>
                {
                    sb.Append(Environment.NewLine);
                    sb.Append("Callref ");
                    sb.Append(c.CallReference);
                    sb.Append(" Line ");
                    sb.Append(c.Line);
                    sb.Append(" ");
                    foreach (string key in c.Changes.Keys)
                    {
                        sb.Append(key);
                        sb.Append("=");
                        sb.Append(c.Changes[key]);
                        sb.Append(" ");
                    }
                });
            }
            return sb.ToString();
        }
    }

    [DataContract]
    public class CallInformation
    {
        /// <summary>
        /// consolidated state
        /// </summary>
        [DataMember]
        public TelephonicState TelephonicState { get; set; }
        [DataMember]
        public long CallReference { get; set; }
        [DataMember]
        public byte[] Correlator { get; set; }
        [DataMember]
        public string Name { get; set; }
        [DataMember]
        public string Number { get; set; }
        [DataMember]
        public string CustomActionNumber { get; set; }
        [DataMember]
        public string CustomActionParameter { get; set; }
        /// <summary>
        ///  full call state
        /// </summary>
        [DataMember]
        public CallState CallState { get; set; }
        [DataMember]
        public CallCapabilities Capabilities { get; set; }
        [DataMember]
        public string LookedUpName { get; set; }
        [DataMember]
        public string E164Number { get; set; }
        [DataMember]
        public string Line { get; set; }
    }

    [DataContract]
    public class CallCapabilitiesChange
    {
        [DataMember]
        public long CallReference { get; set; }
        [DataMember]
        public string Line { get; set; }
        [DataMember]
        public Dictionary<string, bool> Changes { get; set; }

        public CallCapabilitiesChange()
        {
            Changes = new Dictionary<string, bool>();
        }
    }

    [DataContract]
    public class CallInformationUpdate
    {
        [DataMember]
        public long CallReference { get; set; }
        [DataMember]
        public Dictionary<string, string> ChangedInformation { get; set; }

        public CallInformationUpdate()
        {
            ChangedInformation = new Dictionary<string, string>();
        }
    }


And I'm deserializing using the following syntax:

DataContractJsonSerializer callStateChangedSerializer = new DataContractJsonSerializer(typeof(ClientInterface.CallStateChange));


private ClientInterface.CallStateChange deserialize(string data)
{
using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(data)))
{
 ClientInterface.CallStateChange csc = callStateChangedSerializer.ReadObject(ms) as ClientInterface.CallStateChange;
 return csc;
}


Now, if I plug the following data into the deserialize method, it works:

"{\"CallUpdates\":[],\"Changes\":[{\"CallReference\":907,\"Changes\":[{\"Key\":\"deflectAllowed\",\"Value\":true},{\"Key\":\"takeAllowed\",\"Value\":true}],\"Line\":\"3453\"}],\"EndedCalls\":[],\"Line\":\"3453\",\"NewCalls\":[{\"CallReference\":907,\"CallState\":1,\"Capabilities\":null,\"Correlator\":null,\"CustomActionNumber\":\"4771\",\"CustomActionParameter\":null,\"E164Number\":\"4771\",\"Line\":\"3453\",\"LookedUpName\":null,\"Name\":\"WISSLER Patrick\",\"Number\":\"4771\",\"TelephonicState\":3}]}"

If I plug this data into the deserialize method, it craps out:

"{\"CallUpdates\":[],\"Changes\":[{\"Changes\":[{\"Value\":true,\"Key\":\"deflectAllowed\"},{\"Value\":true,\"Key\":\"takeAllowed\"}],\"CallReference\":907,\"Line\":\"3453\"}],\"NewCalls\":[{\"Name\":\"WISSLER Patrick\",\"CustomActionParameter\":null,\"LookedUpName\":null,\"CallReference\":907,\"CustomActionNumber\":\"4771\",\"Correlator\":null,\"Number\":\"4771\",\"TelephonicState\":3,\"Capabilities\":null,\"Line\":\"3453\",\"E164Number\":\"4771\",\"CallState\":1}],\"EndedCalls\":[],\"Line\":\"3453\"}"

If I use the exact same code but make a PC project out of it, the deserialize method works for both.

The issue seems to lie exclusively with what comes first for the dictionary.. key or value. If I run the second set of data through this method designed to swap out key and value for every array, then deserialization works again on M4A:


        public override string ReformatJsonString(string received)
        {
            int start = 1, end = 1, keyStart = 1, keyEnd = 1, valueStart = 1, valueEnd = 1;
            string segment = null, keyName = null, keyValue = null, newSegment = null, newEventData = null;

            newEventData = received;
            while ((start = received.IndexOf("{\"Value", start)) > 0)
            {
                end = received.IndexOf("\"}", start) + 2;
                if (end >= start)
                {
                    segment = received.Substring(start, end - start);
                    keyStart = segment.IndexOf("Key\":") + 5;
                    keyEnd = segment.IndexOf("}", keyStart);
                    if (keyEnd >= keyStart)
                    {
                        keyName = segment.Substring(keyStart, keyEnd - keyStart);
                        valueStart = segment.IndexOf("Value\":") + 7;
                        valueEnd = segment.IndexOf(",");
                        if (valueEnd >= valueStart)
                        {
                            keyValue = segment.Substring(valueStart, valueEnd - valueStart);
                            newSegment = "{\"Key\":" + keyName + ",\"Value\":" + keyValue + "}";
                            newEventData = newEventData.Replace(segment, newSegment);
                        }
                        else
                            OnLogMessage(new LogMessageEventArgs { Message = "Unable to reformat message " + received + ", unable to find , tag after Value\": in segment " + segment, Severity = 2 });
                    }
                    else
                        OnLogMessage(new LogMessageEventArgs { Message = "Unable to reformat message " + received + ", unable to find } tag after Key\": in segment " + segment, Severity = 2 });
                }
                else
                    OnLogMessage(new LogMessageEventArgs { Message = "Unable to reformat message " + received + ", unable to find \"} tag matching {\"Value tag at position 0", Severity = 2 });

                start++;
            }
            return newEventData;
        }

One of the main goals being that you can re-use code, the M4A DataContractJsonSerializer should work the same on PC and Android, so it should properly process the second set of data as well.

I'm targetting API level 14 by the way.
Comment 1 Jonathan Pryor 2012-08-28 12:10:15 UTC
The problem is that our JSON deserialization code assumes that the key is always first and the value is always second, without actually checking the name of the key:

https://github.com/mono/mono/blob/master/mcs/class/System.ServiceModel.Web/System.Runtime.Serialization.Json/JsonSerializationReader.cs#L316

Note You need to log in before you can comment on or make changes to this bug.