简体   繁体   中英

Deserialize object when property has IDictionary<string, string>

I saw this but it doesnot help me.

Deserializing an object with a Dictionary<string, object> property (Newtonsoft.Json)

I am working on WCF Restful service. I have complicated JSON. I have below data contracts

[DataContract]
public class TimestampPackageDC
{
    [DataMember]
    public string Timestamp { get; set; }

    [DataMember]
    public string Company { get; set; }

    [DataMember]
    public string ContactID { get; set; }

    [DataMember]
    public string Area { get; set; }

    [DataMember]
    public string PackageID { get; set; }
} // end of class TimestampPackageDC

[DataContract]
public class TimestampPackageExtraDC: TimestampPackageDC
{
    [DataMember]
    public IDictionary<string, string> ExtraInfo { get; set; }
} // end of class TimestampPackageExtraDC


 [DataContract]
public class GetAllUnprintedItemsResponse: BaseResponse
{
    [DataMember]
    public TimestampPackageDC[] TimestampPackages { get; set; }

    [DataMember]
    public TimestampPackageExtraDC[] TimestampExtraInfoPackages { get; set; }

} 

I am making REST service call like below

GetAllUnprintedItemsResponse upResponse = new GetAllUnprintedItemsResponse();
Request upRequest = new Request() { Name = "abc" };
string url = "http://localhost:2023/myservice/getdata";
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "POST";
request.ContentType = "application/json";
byte[] data = Encoding.UTF8.GetBytes(Newtonsoft.Json.JsonConvert.SerializeObject(upRequest));
request.ContentLength = data.Length;
Stream requestStream = request.GetRequestStream();
requestStream.Write(data, 0, data.Length);
requestStream.Close();

WebResponse response = request.GetResponse();
Stream respStream = response.GetResponseStream();
StreamReader reader = new StreamReader(respStream);
string responseData = reader.ReadToEnd();

//getting error below
Newtonsoft.Json.JsonConvert.PopulateObject(responseData, upResponse);

I am getting JSON string like below

{
    "DocumentException": null,
    "Success": true,
    "TimestampExtraInfoPackages": [{
        "Area ": "AA",
        "Company": "XXX",
        "ContactID": "123",
        "PackageID": "P1",
        "Timestamp": "20090501163433360001",
        "ExtraInfo": [{
                "Key": "Key1",
                "Value": "value1"
            },
            {
                "Key": "Key2",
                "Value": "value2"
            }
        ]
    }],
    "TimestampPackages": []
}

Below is the error I am getting

Cannot deserialize the current JSON array (eg [1,2,3]) into type 'System.Collections.Generic.IDictionary`2[System.String,System.String]' because the type requires a JSON object (eg {"name":"value"}) to deserialize correctly. To fix this error either change the JSON to a JSON object (eg {"name":"value"}) or change the deserialized type to an array or a type that implements a collection interface (eg ICollection, IList) like List that can be deserialized from a JSON array. JsonArrayAttribute can also be added to the type to force it to deserialize from a JSON array. Path 'TimestampExtraInfoPackages[0].ExtraInfo', line 1, position 194.

SOLUTION:

Thanks to Shiva, Brian and taffer. I used taffer advice and it worked. It looks we cannot use IDictionary.

I used below to solve my issue.

public List<KeyValuePair<string, string>> ExtraInfo { get; set; }

I tried below but did not work got same error.

public IDictionary<string, string>[] ExtraInfo { get; set; }

As the error message says the incoming ExtraInfo should be a JSON object but in your example it is a JSON array containing objects with Key and Value properties.

Either feed your service with the expected JSON string:

"ExtraInfo": {
            "Key1": "value1",
            "Key2": "value2"
        }

Or modify your contract to an array or list so it can accept a JSON array:

[DataMember]
public KeyValuePair<string, string>[] ExtraInfo { get; set; }

Your declaration of ExtraInfo should be an array.

public IDictionary<string, string>[] ExtraInfo { get; set; }

instead of

public IDictionary<string, string> ExtraInfo { get; set; }

EDIT

Here's the working code. I tried putting it on dotNetFiddle, but was/am getting some errors on dotnetfiddle.com [ including System.Runtime.Serialization ], hence pasting the working code below.

Note, your ExtraInfo will look like the following. But that's how your JSON also is. If you want to take the values from the Key and Value json keys respectively and construct a single dictionary or keyvaluepair entry, then you'd likely have to write a custom Converter .

在此处输入图片说明

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using Newtonsoft.Json;

namespace SoDeserializeJson_53936668
{
    [System.Runtime.Serialization.DataContract]
    public class TimestampPackageDC
    {
        [DataMember]
        public string Timestamp { get; set; }

        [DataMember]
        public string Company { get; set; }

        [DataMember]
        public string ContactID { get; set; }

        [DataMember]
        public string Area { get; set; }

        [DataMember]
        public string PackageID { get; set; }
    } // end of class TimestampPackageDC

    [DataContract]
    public class TimestampPackageExtraDC : TimestampPackageDC
    {
        [DataMember]
        public IDictionary<string, string>[] ExtraInfo { get; set; }
    } // end of class TimestampPackageExtraDC


    [DataContract]
    public class GetAllUnprintedItemsResponse //: BaseResponse
    {
        [DataMember]
        public TimestampPackageDC[] TimestampPackages { get; set; }

        [DataMember]
        public TimestampPackageExtraDC[] TimestampExtraInfoPackages { get; set; }

    }

    class Program
    {
        static void Main(string[] args)
        {

            var json = @"
            {
                'DocumentException': null,
                'Success': true,
                'TimestampExtraInfoPackages': [{
                    'Area ': 'AA',
                    'Company': 'XXX',
                    'ContactID': '123',
                    'PackageID': 'P1',
                    'Timestamp': '20090501163433360001',
                    'ExtraInfo': [{
                            'Key': 'Key1',
                            'Value': 'value1'
                        },
                        {
                            'Key': 'Key2',
                            'Value': 'value2'
                        }
                    ]
                }],
                'TimestampPackages': []
            }".Replace("'","\"");

            GetAllUnprintedItemsResponse upResponse = new GetAllUnprintedItemsResponse();

            string responseData = json;

            var data = JsonConvert.DeserializeObject<GetAllUnprintedItemsResponse>(json);

            Console.WriteLine(data.TimestampExtraInfoPackages.First().ExtraInfo.Count());

        }
    }
}

You are getting this error because your JSON has an array of key-value pair objects, whereas a dictionary is serialized as a single object.

One solution is to change your ExtraInfo property to be a KeyValuePair<string, string>[] to match the JSON (or change the JSON to match your class) as suggested by @taffer.

However, there is another way. You could keep your ExtraInfo property declared as it is and use a custom JsonConverter to create and populate the dictionary during deserialization.

Here is the code you would need for the converter:

public class KvpArrayToDictionaryConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(IDictionary<string, string>).IsAssignableFrom(objectType);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var dict = new Dictionary<string, string>();
        var array = JArray.Load(reader);
        foreach (var obj in array.Children<JObject>())
        {
            dict.Add((string)obj["Key"], (string)obj["Value"]);
        }
        return dict;
    }

    public override bool CanWrite
    {
        get { return false; }
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

To use the converter, just add a [JsonConverter] attribute to your ExtraInfo property like this:

    [DataMember]
    [JsonConverter(typeof(KvpArrayToDictionaryConverter))]
    public IDictionary<string, string> ExtraInfo { get; set; }

Working demo: https://dotnetfiddle.net/1k7vNA


Another possible solution is to use a ContractResolver to change the contract of the dictionary so that Json.Net sees it as an array of key-value pair objects. See Serialize dictionary as array (of key value pairs) for more info on this approach. However if you go that route you will need to change the declaration of your ExtraInfo property from an IDictionary interface to a concrete Dictionary , otherwise Json.Net will give you an error about not being able to create an instance of an interface.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM