0

I am developing an adapter to a REST service that is returning a JSON string and am wondering if there is a better/more efficient way of doing what I am doing. What I am doing now currently works, but seems a little off in that there are nested foreach loops. I am using JSON.net and this is a console application:

Here is the JSON that is being returned:

{"skillsMetrics":{"201":{"avgTimeToAbandon":0,"totalTimeToAnswer":223,"totalTimeToAbandon":0,"enteredQEng":5,"avgTimeToAnswer":45,"abandonmentRate":0.0,"abandonedEng":0,"connectedEng":5}},"metricsTotals":{"avgTimeToAbandon":0,"totalTimeToAnswer":223,"totalTimeToAbandon":0,"enteredQEng":5,"avgTimeToAnswer":45,"abandonmentRate":0.0,"abandonedEng":0,"connectedEng":5}}

So the issue there is that "201" in this case is an ID field that will change as the Skill changes.

So as I said this code runs, but nested foreach loops seems not within the realm of good coding standards, but I am at a bit of a loss on how else to do this.

Here is my C#:

using (var reader = new StreamReader(response.GetResponseStream()))
{
    var r = reader.ReadToEnd();
    var parsedO = JObject.Parse(r);


    foreach (var item in parsedO)
    {
        var i = item.Key;
        var f = item.Value;

        var parsedO2 = JObject.Parse(f.ToString());

        foreach (var item1 in parsedO2)
        {
            var qHealth = new QueueHealth
            {
                SkillId = item1.Key,
                avgTimeToAbandon = item1.Value.SelectToken("avgTimeToAbandon").Value<dynamic>(),
                totalTimeToAnswer = item1.Value.SelectToken("totalTimeToAnswer").Value<dynamic>(),
                totalTimeToAbandon = item1.Value.SelectToken("totalTimeToAbandon").Value<dynamic>(),
                enteredQEng = item1.Value.SelectToken("enteredQEng").Value<dynamic>(),
                avgTimeToAnswer = item1.Value.SelectToken("avgTimeToAnswer").Value<dynamic>(),
                abandonmentRate = item1.Value.SelectToken("abandonmentRate").Value<dynamic>(),
                abandonedEng = item1.Value.SelectToken("abandonedEng").Value<dynamic>(),
                connectedEng = item1.Value.SelectToken("connectedEng").Value<dynamic>()
            };
        }
    }

EDIT:

I forgot to put my class in here as well. The challenge is I need to pass that SkillID with it in order to do some data aggregation etc on the database side of the house. The data is going to be used in a reporting tool

public class QueueHealth
{
    public string SkillId { get; set; }
    public int avgTimeToAbandon { get; set; }
    public int totalTimeToAnswer { get; set; }
    public int totalTimeToAbandon { get; set; }
    public int enteredQEng { get; set; }
    public int avgTimeToAnswer { get; set; }
    public double abandonmentRate { get; set; }
    public int abandonedEng { get; set; }
    public int connectedEng { get; set; }
}

public class QueueHealthContext : DbContext
{
    public QueueHealthContext() : base(ConfigWrapper.AppConnectionString) { }
    public DbSet<QueueHealth> QueuHealthTemp { get; set; }
}

}

Here is the multiple Skill JSON that is being returned:

{"skillsMetrics":{"64":{"avgTimeToAbandon":0,"totalTimeToAnswer":56,"totalTimeToAbandon":0,"enteredQEng":19,"avgTimeToAnswer":3,"abandonmentRate":0.0,"abandonedEng":0,"connectedEng":19},"201":{"avgTimeToAbandon":0,"totalTimeToAnswer":470,"totalTimeToAbandon":0,"enteredQEng":5,"avgTimeToAnswer":67,"abandonmentRate":0.0,"abandonedEng":0,"connectedEng":7},"65":{"avgTimeToAbandon":0,"totalTimeToAnswer":56,"totalTimeToAbandon":0,"enteredQEng":5,"avgTimeToAnswer":11,"abandonmentRate":0.0,"abandonedEng":0,"connectedEng":5},"66":{"avgTimeToAbandon":0,"totalTimeToAnswer":3,"totalTimeToAbandon":0,"enteredQEng":2,"avgTimeToAnswer":2,"abandonmentRate":0.0,"abandonedEng":0,"connectedEng":2},"202":{"avgTimeToAbandon":0,"totalTimeToAnswer":19,"totalTimeToAbandon":0,"enteredQEng":5,"avgTimeToAnswer":4,"abandonmentRate":0.0,"abandonedEng":0,"connectedEng":5},"199":{"avgTimeToAbandon":932,"totalTimeToAnswer":2802,"totalTimeToAbandon":2796,"enteredQEng":5,"avgTimeToAnswer":934,"abandonmentRate":0.5,"abandonedEng":3,"connectedEng":3},"198":{"avgTimeToAbandon":203,"totalTimeToAnswer":10488,"totalTimeToAbandon":405,"enteredQEng":178,"avgTimeToAnswer":62,"abandonmentRate":0.01,"abandonedEng":2,"connectedEng":168},"192":{"avgTimeToAbandon":0,"totalTimeToAnswer":41,"totalTimeToAbandon":0,"enteredQEng":3,"avgTimeToAnswer":10,"abandonmentRate":0.0,"abandonedEng":0,"connectedEng":4},"194":{"avgTimeToAbandon":0,"totalTimeToAnswer":2,"totalTimeToAbandon":0,"enteredQEng":1,"avgTimeToAnswer":2,"abandonmentRate":0.0,"abandonedEng":0,"connectedEng":1},"100":{"avgTimeToAbandon":0,"totalTimeToAnswer":68,"totalTimeToAbandon":0,"enteredQEng":19,"avgTimeToAnswer":3,"abandonmentRate":0.0,"abandonedEng":0,"connectedEng":20},"169":{"avgTimeToAbandon":0,"totalTimeToAnswer":2497,"totalTimeToAbandon":0,"enteredQEng":55,"avgTimeToAnswer":47,"abandonmentRate":0.0,"abandonedEng":0,"connectedEng":53},"168":{"avgTimeToAbandon":10,"totalTimeToAnswer":1728,"totalTimeToAbandon":10,"enteredQEng":37,"avgTimeToAnswer":48,"abandonmentRate":0.03,"abandonedEng":1,"connectedEng":36},"101":{"avgTimeToAbandon":0,"totalTimeToAnswer":0,"totalTimeToAbandon":0,"enteredQEng":1,"avgTimeToAnswer":0,"abandonmentRate":0.0,"abandonedEng":0,"connectedEng":1},"175":{"avgTimeToAbandon":0,"totalTimeToAnswer":5837,"totalTimeToAbandon":0,"enteredQEng":4,"avgTimeToAnswer":1459,"abandonmentRate":0.0,"abandonedEng":0,"connectedEng":4},"174":{"avgTimeToAbandon":1201,"totalTimeToAnswer":2144,"totalTimeToAbandon":1201,"enteredQEng":3,"avgTimeToAnswer":1072,"abandonmentRate":0.33,"abandonedEng":1,"connectedEng":2},"96":{"avgTimeToAbandon":0,"totalTimeToAnswer":18,"totalTimeToAbandon":0,"enteredQEng":5,"avgTimeToAnswer":4,"abandonmentRate":0.0,"abandonedEng":0,"connectedEng":5},"97":{"avgTimeToAbandon":0,"totalTimeToAnswer":352,"totalTimeToAbandon":0,"enteredQEng":4,"avgTimeToAnswer":70,"abandonmentRate":0.0,"abandonedEng":0,"connectedEng":5},"167":{"avgTimeToAbandon":40,"totalTimeToAnswer":8661,"totalTimeToAbandon":40,"enteredQEng":140,"avgTimeToAnswer":62,"abandonmentRate":0.01,"abandonedEng":1,"connectedEng":140},"46":{"avgTimeToAbandon":0,"totalTimeToAnswer":87,"totalTimeToAbandon":0,"enteredQEng":33,"avgTimeToAnswer":2,"abandonmentRate":0.0,"abandonedEng":0,"connectedEng":38},"45":{"avgTimeToAbandon":0,"totalTimeToAnswer":74,"totalTimeToAbandon":0,"enteredQEng":24,"avgTimeToAnswer":2,"abandonmentRate":0.0,"abandonedEng":0,"connectedEng":30},"55":{"avgTimeToAbandon":352,"totalTimeToAnswer":22025,"totalTimeToAbandon":1407,"enteredQEng":396,"avgTimeToAnswer":55,"abandonmentRate":0.01,"abandonedEng":4,"connectedEng":402},"59":{"avgTimeToAbandon":0,"totalTimeToAnswer":1463,"totalTimeToAbandon":0,"enteredQEng":39,"avgTimeToAnswer":37,"abandonmentRate":0.0,"abandonedEng":0,"connectedEng":40},"57":{"avgTimeToAbandon":0,"totalTimeToAnswer":3069,"totalTimeToAbandon":0,"enteredQEng":44,"avgTimeToAnswer":68,"abandonmentRate":0.0,"abandonedEng":0,"connectedEng":45},"56":{"avgTimeToAbandon":0,"totalTimeToAnswer":6740,"totalTimeToAbandon":0,"enteredQEng":114,"avgTimeToAnswer":58,"abandonmentRate":0.0,"abandonedEng":0,"connectedEng":116},"182":{"avgTimeToAbandon":0,"totalTimeToAnswer":6,"totalTimeToAbandon":0,"enteredQEng":3,"avgTimeToAnswer":2,"abandonmentRate":0.0,"abandonedEng":0,"connectedEng":3},"62":{"avgTimeToAbandon":0,"totalTimeToAnswer":871,"totalTimeToAbandon":0,"enteredQEng":1,"avgTimeToAnswer":871,"abandonmentRate":0.0,"abandonedEng":0,"connectedEng":1},"61":{"avgTimeToAbandon":0,"totalTimeToAnswer":2015,"totalTimeToAbandon":0,"enteredQEng":1,"avgTimeToAnswer":672,"abandonmentRate":0.0,"abandonedEng":0,"connectedEng":3},"180":{"avgTimeToAbandon":0,"totalTimeToAnswer":6,"totalTimeToAbandon":0,"enteredQEng":3,"avgTimeToAnswer":2,"abandonmentRate":0.0,"abandonedEng":0,"connectedEng":3},"60":{"avgTimeToAbandon":849,"totalTimeToAnswer":11973,"totalTimeToAbandon":3394,"enteredQEng":16,"avgTimeToAnswer":1330,"abandonmentRate":0.31,"abandonedEng":4,"connectedEng":9}},"metricsTotals":{"avgTimeToAbandon":578,"totalTimeToAnswer":83569,"totalTimeToAbandon":9253,"enteredQEng":1165,"avgTimeToAnswer":71,"abandonmentRate":0.01,"abandonedEng":16,"connectedEng":1170}}
MattV
  • 43
  • 6
  • 2
    Wow! Jon Skeet editing this post! I better pay close attention ... – Darek Jul 10 '14 at 22:13
  • The way it should work, is you create a custom class (like `QueueHealth`) and attribute its properties with the json field, effectively making a mapping. Then it will deserialize directly into your object. – crthompson Jul 10 '14 at 22:13
  • @Darek, you cant be a fanboy if you cant spell Jon's name right. ;) – crthompson Jul 10 '14 at 22:14
  • Damn auto-correct did this to me ... @paqogomez – Darek Jul 10 '14 at 22:15
  • I added my class code now too. The issue is how to get the SkillID into that class. Or do I make that a separate class with the other pieces as its properties... – MattV Jul 10 '14 at 22:21

2 Answers2

4

I would deserialize to concrete classes

var obj = JsonConvert.DeserializeObject<MyObject>(json);

public class MyObject
{
    public Dictionary<string,Item> SkillsMetrics { set; get; }
    public Item MetricsTotals { set; get; }
}

public class Item
{
    public int avgTimeToAbandon { get; set; }
    public int totalTimeToAnswer { get; set; }
    public int totalTimeToAbandon { get; set; }
    public int enteredQEng { get; set; }
    public int avgTimeToAnswer { get; set; }
    public double abandonmentRate { get; set; }
    public int abandonedEng { get; set; }
    public int connectedEng { get; set; }
}

EDIT

foreach (var item in obj.SkillsMetrics)
{
    Console.WriteLine("SkillId:" + item.Key + " => " + item.Value.totalTimeToAnswer);
}
Sergey Berezovskiy
  • 232,247
  • 41
  • 429
  • 459
EZI
  • 15,209
  • 2
  • 27
  • 33
  • I have something like that, but how would I incorporate that SkillID in there as well? Thanks for your response! – MattV Jul 10 '14 at 22:20
  • indeed, thats the proper way of doing it - maybe add a few specialized JSON-annotations, validate input and stabilize the output and JSON is quite the useful tool - especially with libraries like these – specializt Jul 10 '14 at 22:20
  • I also highly prefer deserializing to concrete classes. I would only do the more dynamic parsing when the input is highly variant and I need to make run time decisions about how to handle it. – evanmcdonnal Jul 10 '14 at 22:20
  • @user3565811 Just try the code, *SkillID* is in the **Keys** of SkillsMetrics. (It is a `Dictionary`) – EZI Jul 10 '14 at 22:23
  • @EZI I did try it. It does everything, but pass the SkillID into the Object. – MattV Jul 10 '14 at 22:30
  • @user3565811 As I said, look into the keys of Dictionary... See the edit – EZI Jul 10 '14 at 22:30
  • @SergeyBerezovskiy you also removed the thanks about the answer. No problem, I took a snapshot of this page and whole conversation. You still abuse your privileges. BTW, How do you think to remove the 27 edits of my answers just to be able to undo your 27 downvotes? – EZI Jul 11 '14 at 09:46
0

So I've used Newtonsoft's Json converter to get a dynamic object:

        string s =
            @"{""skillsMetrics"":{""201"":{""avgTimeToAbandon"":0,""totalTimeToAnswer"":223,""totalTimeToAbandon"":0,""enteredQEng"":5,""avgTimeToAnswer"":45,""abandonmentRate"":0.0,""abandonedEng"":0,""connectedEng"":5}},""metricsTotals"":{""avgTimeToAbandon"":0,""totalTimeToAnswer"":223,""totalTimeToAbandon"":0,""enteredQEng"":5,""avgTimeToAnswer"":45,""abandonmentRate"":0.0,""abandonedEng"":0,""connectedEng"":5}}";
        dynamic d = JsonConvert.DeserializeObject(s);

Interestingly, it ended up with enumerable of two elements, or two properties.

So if your concrete class implementation will follow what has been dynamically generated, you should not have a problem deserializing.

enter image description here

However, I think I understand your problem, that you have a "property" called "201", or whatever other number is given.

enter image description here

Is there any way you can change the JSON result to be more predictable?

Darek
  • 4,687
  • 31
  • 47
  • Unfortunately this is coming from a 3rd party that has developed this API, I am just facilitating the data getting put into a database. The data format will always be like this. The biggest issue is that I need that "201" as its the identifier for the data. I have added what the JSON looks like with multiple result sets returned here: – MattV Jul 10 '14 at 22:36
  • I'm up-voting this answer because I recently had a situation where the 3rd party service changed the json object out from under us which broke my (and other people's) app. Not saying that will happen to you, but if it does keep this dymanic object technique in your back pocket. – Michael Cook Jul 11 '14 at 14:52