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)

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

Notice (2018-05-24): bugzilla.xamarin.com is now in read-only mode.

Please join us on Visual Studio Developer Community and in the Xamarin and Mono organizations on GitHub to continue tracking issues. Bugzilla will remain available for reference in read-only mode. We will continue to work on open Bugzilla bugs, copy them to the new locations as needed for follow-up, and add the new items under Related Links.

Our sincere thanks to everyone who has contributed on this bug tracker over the years. Thanks also for your understanding as we make these adjustments and improvements for the future.


Please create a new report for Bug 6721 on GitHub or Developer Community if you have new information to add and do not yet see a matching new report.

If the latest results still closely match this report, you can use the original description:

  • Export the original title and description: GitHub Markdown or Developer Community HTML
  • Copy the title and description into the new report. Adjust them to be up-to-date if needed.
  • Add your new information.

In special cases on GitHub you might also want the comments: GitHub Markdown with public comments

Related Links:
Status:
NEW

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