1

I need to deserialize the following json:

    {
      "1": {
        "oid": "46",
        "order": "SD9999999999999",
        "date": "2015-08-18 14:17:05",
        "item": {
          "0": {
            "guid": "DEF456"
            "price": "100.00"
          },
          "1": {
            "guid": "ABC123",
            "price": "99.99"
          }
        }
      },
      "2": {
        "oid": "765",
        "order": "SD0000000000000",
        "date": "2015-08-18 14:17:05",
        "item": {
          "0": {
            "guid": "GHI789"
            "price": "10.00"
          },
          "1": {
            "guid": "XYZ123",
            "price": "9.99"
          }
       }
    },
    "store": 111,
    "time": "2015-09-01 17:51:22"
  }

The number of orders is unknown as well as the number of items in each order.

I have tried making a list of orders and dynamically looping through and JsonConvert.DeserializeObject() thhen adding each to the list, but this doesn't account for the items being dynamic and items end up equalling null. Any thoughts?

qwerty123
  • 41
  • 1
  • 8
  • Possible duplicate of [Deserialize JSON into C# dynamic object?](http://stackoverflow.com/questions/3142495/deserialize-json-into-c-sharp-dynamic-object) – Heretic Monkey Jan 13 '17 at 19:07
  • Your main problem is that that `"item"` property is an object (`{ .. }`) with indexed keys instead of just an array (`[ .. ]`). If that weren't the case, it could have been deserialized to an array of type `class ItemPrice { public string guid {get;set;} public string price {get;set;}`. Do you have control of the API? – Maximilian Gerhardt Jan 13 '17 at 19:10
  • Use `JObject`/`JToken` or you might be able to deserialze to `Dictionary` objects (since you don't have arrays). – crashmstr Jan 13 '17 at 19:11
  • You can deserialize into a `Dictionary` (or `SortedDictionary`) as explained in [Create a strongly typed c# object from json object with ID as the name](http://stackoverflow.com/a/34213724/3744182). In fact, I think that's a duplicate. Agree? – dbc Jan 13 '17 at 22:39
  • @MikeMcCaughan it is not a duplicate, because those answers are only standard answers, they don't work with dynamic objects – qwerty123 Jan 16 '17 at 19:39
  • @MaximilianGerhardt yes, I understand that as well as the root object being the main problems, no I don't have any access to changing the api – qwerty123 Jan 16 '17 at 19:40
  • @crashmstr can you post an answer, I have tried a couple approaches with JToken and Dictioaries, but no luck so far – qwerty123 Jan 16 '17 at 19:41
  • @dbc not a duplicate for two reasons, 1. my json has additional fields in the rootobject and I get errors when I try it this way. 2. Deeper in the tree I have more dynamic fields and I am not sure how to go about deserializing those as well, I think Dictionary is the right approach, just everything I try I get some type of nullpointer exception or can't deserialize it correctly – qwerty123 Jan 16 '17 at 19:44
  • "they don't work with dynamic objects"... Umm, that's exactly what they say they do work with... http://stackoverflow.com/a/9326146/215552 shows it deserializing the JSON into a dynamic object. Perhaps you can [edit] your question to show how those solutions did not meet your needs. – Heretic Monkey Jan 16 '17 at 19:50
  • @MikeMcCaughan dynamic in the sense that the object needing deserialization has fields(the "item" field as well as the root) that the number of them are unknown before the deserialization, therefore the regular field names don't apply(idk to deserialize as 10 or 5 or 2 object fields) – qwerty123 Jan 16 '17 at 20:04
  • 1
    The answers, however, will deserialize any number of fields in the JSON into the same number of fields in a C# dynamic object, if I'm reading it correctly. You'll need to figure out how to access those dynamic objects, and that's essentially what @crashmstr has done in their answer. – Heretic Monkey Jan 16 '17 at 20:10
  • `dynamic` is a huge pain. You need to cast anything and everything and any kind of LINQ is very ugly. While `JToken` is more verbose with `["..."]` and still needs casting, LINQ can be much simpler. – crashmstr Jan 16 '17 at 20:12
  • @crashmstr - you're right. Then take a look at [How to deserialize a child object with dynamic (numeric) key names?](http://stackoverflow.com/a/40094403/3744182) which deserializes JSON with a mixture of known and unknown properties where the unknown properties are typed. Apply the solution for both the root and `"item"` objects. – dbc Jan 16 '17 at 20:22
  • you seems to be telling everyone that their suggestion is wrong and that this is not a duplicate, but you have not shown what you have tried, nor have you offered anything up of meaningful value to be able to help you fully. I suggest you edit the question and show your own .Net code of what you have tried so that we can see what you are trying and help you where you're going wrong. – Simon Price Jan 17 '17 at 13:50

1 Answers1

2

This works using JObject/JToken (after fixing the missing , in your json).

var json = System.IO.File.ReadAllText("test.json");
var jobj = Newtonsoft.Json.Linq.JObject.Parse(json);

//exclude "store" and "time"
var orders = jobj.Children().Where(x => x.Path != "store" && x.Path != "time").ToList();

//iterate over orders
foreach (var order in orders)
{
    //"name" of order
    var orderName = order.Path;
    //and contents
    var orderItems = order.First()["item"].ToList();

    foreach (var item in orderItems)
    {
        var itemName = item.Path;
        var itemContents = item.First();

        var guid = (String)itemContents["guid"];
        var price = Double.Parse((String)itemContents["price"]);
    }
}

And for completeness, also code deserializing to a Dictionary<String, dynamic>

var dynObj = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<String, dynamic>>(json);
var dynOrders = dynObj.Where(x => x.Key != "store" && x.Key != "time").ToList();

foreach (dynamic order in dynOrders)
{
    var orderName = order.Key;
    var orderItems = order.Value.item;

    foreach (var item in orderItems)
    {
        var itemName = item.Name;
        var guid = (String)item.Value.guid;
        var price = Double.Parse((String)item.Value.price);
    }
}

I will say that for both of these, I had to set breakpoints and examine the objects and see what I had to work with and what would work to get that data. "Dictionary" style objects are more awkward in C# compared to arrays. I did find another way to do this via this answer. While the most verbose, also the most consistent across object levels.

//"force" to use IDictionary interface
IDictionary<string, JToken> dict = Newtonsoft.Json.Linq.JObject.Parse(json);
var dictOrders = dict.Where(x => x.Key != "store" && x.Key != "time").ToList();

foreach (var order in dictOrders)
{
    var orderName = order.Key;
    var orderProps = (IDictionary<string, JToken>)order.Value;

    var oid = Int32.Parse((String)orderProps["oid"]);
    var orderX = (String)orderProps["order"];
    var date = DateTime.Parse((String)orderProps["date"]);
    var orderItems = (IDictionary<string, JToken>)orderProps["item"];

    foreach (var item in orderItems)
    {
        var itemName = item.Key;
        var itemContents = (IDictionary<string, JToken>)item.Value;

        var guid = (String)itemContents["guid"];
        var price = Double.Parse((String)itemContents["price"]);
    }
}

By using this pattern, you should be able to take any JToken, cast it to a IDictionary<string, JToken>, then deal with its contents in a consistent manner using Key for the property name and Value for the contents (which you would also cast to IDictionary so you can descent the object as far as you need).

Community
  • 1
  • 1
crashmstr
  • 28,043
  • 9
  • 61
  • 79
  • can you add the RootObject and other classes formatting, thanks – qwerty123 Jan 16 '17 at 21:03
  • 1
    You have no `RootObject` shown in your json example, so I don't know what else is there. This code shows you how to get the number of orders and number of items in each order with the given json (and is what you specifically ask about). – crashmstr Jan 16 '17 at 21:07