Problem with deserializing JSON on datamember “__t

2019-01-15 01:10发布

问题:

In short, i'm trying to deserialize a JSON response from the Bing Maps Geocoding REST API,

I created my Response Class, and now when I'm trying to actually deserialize a response, i'm getting the following error:

Type '{0}' with data contract name '{1}:{2}' is not expected. Consider using a DataContractResolver or add any types not known statically to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to DataContractSerializer.

it's trying to deserialize this line of JSON, and fails:

"__type": "Location:http:\/\/schemas.microsoft.com\/search\/local\/ws\/rest\/v1",

My response class looks like this

        [DataContract]
        public class GeoResponse
        {
            [DataMember(Name = "statusDescription")]
            public string StatusDescription { get; set; }
            [DataMember(Name = "statusCode")]
            public string StatusCode { get; set; }
            [DataMember(Name = "resourceSets")]
            public ResourceSet[] resourceSets { get; set; }

            [DataContract]
            public class ResourceSet
            {


                [DataMember(Name = "__type", IsRequired=false)]
                public string type { get; set; }

                [DataMember(Name = "estimatedTotal")]
                public string EstimatedTotal { get; set; }

                [DataMember(Name = "resources")]
                public List<Resources> resources { get; set; }

                [DataContract]
                public class Resources
                {
                    [DataMember(Name = "name")]
                    public string Name { get; set; }

                    [DataMember(Name = "point")]
                    public Point point { get; set; }

                    [DataContract]
                    public class Point
                    {
                        [DataMember(Name = "type")]
                        public string Type { get; set; }

                        [DataMember(Name = "coordinates")]
                        public string[] Coordinates { get; set; }
                    }

                    [DataMember(Name = "address")]
                    public Address address { get; set; }

                    [DataContract]
                    public class Address
                    {
                        [DataMember(Name = "addressLine")]
                        public string AddressLine { get; set; }

                        [DataMember(Name = "countryRegion")]
                        public string CountryRegion { get; set; }

                        [DataMember(Name = "formattedAddress")]
                        public string FormattedAddress { get; set; }

                        [DataMember(Name = "locality")]
                        public string Locality { get; set; }

                        [DataMember(Name = "postalCode")]
                        public string PostalCode { get; set; }
                    }

                    [DataMember(Name = "confidence")]
                    public string Confidence { get; set; }

                    [DataMember(Name = "entityType")]
                    public string EntityType { get; set; }
                }

            }
        }

    }

My method i'm using to deserialize my JSON response:

private static GeoResponse CallGeoWS(string address)
{
    string url = string.Format(
            "http://dev.virtualearth.net/REST/v1/Locations?q={0}&key={1}",
            HttpUtility.UrlEncode(address), bingkey
            );
    var request = (HttpWebRequest)HttpWebRequest.Create(url);
    request.Headers.Add(HttpRequestHeader.AcceptEncoding, "gzip,deflate");
    request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
    DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(GeoResponse));            
    var res = (GeoResponse)serializer.ReadObject(request.GetResponse().GetResponseStream());
    return res;
}

回答1:

First of all, please note that the method you are quoting ( http://dev.virtualearth.net/REST/v1/Locations?q=Wiertzstraat+43+1047+Brussel&key=BingMapsKey ), generates a different response compared to the one you are trying to map with your DataContract class. The response is described here: http://msdn.microsoft.com/en-us/library/ff701711.aspx

I have created a DataContract for that response:

[DataContract]
public class LocationQueryResponse
{
    [DataMember]
    public string authenticationResultCode { get; set; }
    [DataMember]
    public string brandLogoUri { get; set; }
    [DataMember]
    public string copyright { get; set; }
    [DataMember]
    public string statusCode { get; set; }
    [DataMember]
    public string statusDescription { get; set; }
    [DataMember]
    public string traceId { get; set; }

    [DataMember]
    public ResourceSet[] resourceSets { get; set; }

    [DataContract]
    public class ResourceSet
    {
        [DataMember]
        public int estimatedTotal { get; set; }

        [DataMember]
        public Resource[] resources { get; set; }

        [DataContract(Namespace = "http://schemas.microsoft.com/search/local/ws/rest/v1", Name="Location")]
        public class Resource
        {
            [DataMember]
            public string __type { get; set; }

            [DataMember]
            public double[] bbox { get; set; }

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

            [DataMember]
            public Point point { get; set; }

            [DataContract]
            public class Point
            {
                [DataMember]
                public string type { get; set; }

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

            [DataMember]
            public Address address { get; set; }

            [DataContract]
            public class Address
            {
                [DataMember]
                public string addressLine { get; set; }
                [DataMember]
                public string adminDistrict { get; set; }
                [DataMember]
                public string adminDistrict2 { get; set; }
                [DataMember]
                public string countryRegion { get; set; }
                [DataMember]
                public string formattedAddress { get; set; }
                [DataMember]
                public string locality { get; set; }
                [DataMember]
                public string postalCode { get; set; }
            }

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

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

    }
}

At first, even if I created a correct DataContract, it did not work and it generated the same exception that you presented. After some research I found that the "__type" field has a special meaning for DataContractJsonSerializer, denoting the type to which the object should be deserialized. In order to make this work I added Name and Namespace attributes to the DataContract attribute of the Resource class (please check code above).

I have quite some experience with WCF and JSON and never came across this problem before. It seems to be quite an obscure one and the __type field doesn't seem to be standard compliant, but rather a Microsoft specific feature. Quite annoying is the fact that the __type field seem to only in some specific situations. For example, if in the JSON document you have white space before it, the deserializer would ignore it and not throw any exception. I had such a white space in the documents I have initially used for testing and this why I did not get errors at that point.

Hope this one finally helped. :)