32

I am trying to use the Northwind OData service:

http://services.odata.org/V3/OData/OData.svc/Products?$format=json

and deserialize it to a collection of products:

    using (var client = new HttpClient())
    {
        HttpResponseMessage response = await client.GetAsync(new Uri(url));
        ObservableCollection<Product> products = await response.Content.ReadAsAsync<ObservableCollection<Product>>();
    }

But the serializer doesn't seem to like the odata.metadata part and the fact that there are 2 odata.type records there (not sure what they are).

Is there an easy way to do this?

Markus Jarderot
  • 86,735
  • 21
  • 136
  • 138
Graeme
  • 2,597
  • 8
  • 37
  • 50

6 Answers6

45

Using Json.Net

using (var client = new HttpClient())
{
    var json = await client.GetStringAsync("http://services.odata.org/V3/OData/OData.svc/Products?$format=json");
    var odata = JsonConvert.DeserializeObject<OData>(json);
}

public class Value
{
    [JsonProperty("odata.type")]
    public string Type { set; get; }
    public int ID { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public DateTime ReleaseDate { get; set; }
    public DateTime? DiscontinuedDate { get; set; }
    public int Rating { get; set; }
    public double Price { get; set; }
}

public class OData
{
    [JsonProperty("odata.metadata")]
    public string Metadata { get; set; }
    public List<Value> Value { get; set; }
}
L.B
  • 114,136
  • 19
  • 178
  • 224
  • BTW, will all oData services return data in this shape? So I can assume Northwind customers will be similar, or do I have to handcraft each service? – Graeme Nov 08 '14 at 18:17
  • 1
    @Graeme No idea about oData services. I just prepared this sample according to your url in question. – L.B Nov 08 '14 at 18:18
  • You could also deseirialize to dynamic. – Christopher W. Brandsdal Nov 08 '14 at 18:23
  • 1
    @Brandsdal How would solve it `odata.type` problem? `type` is not a property of `odata` object. it is property's name. – L.B Nov 08 '14 at 18:30
  • 1
    @Graeme you can't rely on oData services returning data in this shape. – lukkea Mar 19 '15 at 14:16
  • 1
    @L.B, I think that Brandsdal was talking about to use generic in OData class like this: public List Value { get; set; } –  Nov 26 '16 at 18:13
  • For cases like MS Graph api, there's an @ suffix. Had to replace that manually in response before deserializing to work properly – NitinSingh Aug 21 '18 at 14:17
35

Define a class for the response from odata (Its a generic definition so you can use this with any type):

internal class ODataResponse<T>
 {
    public List<T> Value { get; set; }
 }

Deserialize like this:

using (var client = new HttpClient())
 {
     HttpResponseMessage response = await client.GetAsync(new Uri(url));
     var json = await response.Content.ReadAsStringAsync();
     var result = JsonConvert.DeserializeObject<ODataResponse<Product>>(json);
     var products = result.Value;
 }
ozanmut
  • 2,898
  • 26
  • 22
15

If you are using Visual Studio there is a fantastic CLR Class Generation feature built in.

  1. Copy an OData payload into your clipboard
  2. In Visual Studio select the menu option Edit --> Paste Special --> Paste JSON as Object classes

You can then use Json.NET to deserialize into these classes (as described in L.B's answer).

ScottB
  • 1,363
  • 2
  • 14
  • 24
  • If you only see "Paste XML Classes" menu option then paste your JSON anyways once using the paste as xml menu. The IDE sees this and then adds the Paste JSON as Classes menu item. It will paste where your cursor is at - so a new blank dummy class file works well as a paste target. – Sql Surfer Oct 18 '17 at 12:25
  • I've been using a web site to do this for ages and this was such a cool tip. VS handles a few odd elements much better than the site. – goneos Aug 13 '19 at 22:48
5

There are .NET client for directly consuming OData services. For V3 odata service, you can try with Simple.OData.Client , ODataLib for OData v1-3. For V3 OData service, you can try with OData Client Code Generator. Other libraries for OData client, you can refer to http://www.odata.org/libraries/ .

QianLi
  • 1,088
  • 12
  • 22
4

Another potential way to manage the deserialisation problem caused by the odata.metadata part is to request that the odata response doesn't contain the metadata. This can be done with a default request header in the http client:

client.DefaultRequestHeaders.Add("Accept", "application/json;odata.metadata=none");

Which allows the object to then be deserialised with ReadAsAsync:

var products = response.Content.ReadAsAsync<Dictionary<string, ObservableCollection<Product>>>().Result["value"]

This seems much cleaner than having to write another class to handle the response. Using .Result might not be the best way as the code isn't then asynchronous, but it wasn't important in my application and made the code occupy fewer lines.

Rand0mChar
  • 41
  • 2
3

Another way to handle it is to use JObject Data

 JsonConvert.DeserializeObject<JObject>(response)["value"].ToObject<List<Product>>()
kipras
  • 71
  • 3