1

I am trying to parse a JSON feed using LINQ and can't quite wrap a list of objects/values into a reasonable class.

My JSON looks like:

{
   "Project":[
      {
         "ID":"XY1212",
         "Name":"Some Name",
         "Description":"U.S. No 2 Diesel Retail Prices",
         "Manager":"Nora Sims",
         "Dept":"HR",
         "Updated":"2014-07-22",
         "Statistics":[
            [
               "20140722",
               32.22
            ],
            [
               "20140721",
               55
            ],
            [
               "20140720",
               343
            ],
            [
               "20140519",
               43
            ],
            [
               "20140421",
               3.971
            ],
            [
               "20140211",
               40.2
            ],
            [
               "20140210",
               17
            ],
            [
               "20140209",
               16
            ]
         ]
      }
   ]
}

From the above JSON, I have the following class structure:

public class Project
{
    public string ID { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public string Manager { get; set; }
    public string Dept { get; set; }
    public string Updated { get; set; }
    public List<List<object>> Statistics { get; set; }
}

public class RootObject
{
    public List<Project> Project { get; set; }
}

public class Statistic
{
    public Datetime ProjectDate { get; set; }
    public Decimal Sale { get; set; }
}

I am trying to parse the feed and just want the "Statistics" data, but not sure how to get all of the values into the collection of "Statistics":

HttpClient() http = new HttpClient();
var json = await http.GetStringAsync(uri);

JObject jo = JObject.Parse(json);
var jList = from values in jo["Project"].Children()["Statistics"]
    select values;

When I inspect jList via the following loop:

foreach (var stat in jList)
{
    Console.WriteLine(stat);
}

I can "see" all of the values, but it only loops once, i.e. jList is only one big [0] with a "value" of all of the [x, y], [x1, y1], ..., i.e. looks like an array of one dimension with many 2D arrays inside it.

I want to loop through all of the "arrays", I believe that's what they are, within the [0] I see in Visual Studio while debugging.

Any advice appreciated.

Brian Rogers
  • 125,747
  • 31
  • 299
  • 300
user118190
  • 2,139
  • 7
  • 29
  • 45
  • Do you get any error? or just that there is no data parsed? – bit Aug 05 '14 at 04:01
  • 1
    I cant remember the exact code, but your Project Class should have a List object in it, and Statistics should be its own class. Kind of like what you have done with RootObject – Seige Aug 05 '14 at 04:06
  • 1
    Try `var proj = jo["Project"].ToObject();`. This way you get a `Project` instance which you can easily get `Statistics` from. – Alex Kiselev Aug 05 '14 at 04:19
  • @bit - I have added a Statistic class and the values I am observing while debugging in Visual Studio. – user118190 Aug 05 '14 at 04:21
  • Can you put up the code you are using to loop through? Should probably be something like.. foreach(Project p in RootObject) Foreach(Statistic s in p.Statistics) s.projectdate; – Seige Aug 05 '14 at 04:27
  • @Seige - I actually did add the foreach loop but had forgotten to wrap around a "code" block. You should be able to view it now better. Thx. – user118190 Aug 05 '14 at 04:33
  • You are using JSON.Net? Why don't you use the Deserialize that it has? http://stackoverflow.com/questions/2546138/deserializing-json-data-to-c-sharp-using-json-net List Projects = JsonConvert.DeserializeObject(jo); It may be root projects. – Seige Aug 05 '14 at 04:36
  • @AlexKiselev - I tried as you suggested and I got an exception: Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'Project' because the type requires a JSON object (e.g. {"name":"value"}) to deserialize correctly. To fix this error either change the JSON to a JSON object (e.g. {"name":"value"}) or change the deserialized type to an array or a type that implements a collection interface (e.g. 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. – user118190 Aug 05 '14 at 04:37
  • gotcha. that's not going to work then.. – Seige Aug 05 '14 at 04:39
  • @Seige - I just updated the above code. I am using JSON.NET with the following: HttpClient() http = new HttpClient(); var json = await http.GetStringAsync(uri); – user118190 Aug 05 '14 at 04:41
  • @user118190 I added an answer that should help. – Brian Rogers Aug 08 '14 at 02:52

2 Answers2

2

You can solve this easily by making a custom JsonConverter for your Statistic class. It will deal with the unconventional JSON and allow you to define your Project class the way you'd really like to have it.

Here is the code for the converter:

class StatisticConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(Statistic));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JArray array = JArray.Load(reader);
        return new Statistic
        {
            ProjectDate = DateTime.ParseExact(array[0].ToString(), "yyyyMMdd", 
                            System.Globalization.CultureInfo.InvariantCulture),
            Sale = array[1].ToObject<decimal>()
        };
    }

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

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

To use the converter, we just need to make a couple of minor changes to your classes. First, change the Statistics property to be a List<Statistic> instead of a List<List<object>>. (Don't worry that it doesn't match the JSON-- that's what the converter is for.)

public class Project
{
    public string ID { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public string Manager { get; set; }
    public string Dept { get; set; }
    public string Updated { get; set; }
    public List<Statistic> Statistics { get; set; }
}

Next decorate your Statistic class with a [JsonConverter] attribute to tie it to the custom converter:

[JsonConverter(typeof(StatisticConverter))]
public class Statistic
{
    public DateTime ProjectDate { get; set; }
    public Decimal Sale { get; set; }
}

That's it! Now you can deserialize as normal, and you'll get a list of statistics the way you want.

RootObject root = JsonConvert.DeserializeObject<RootObject>(json);

Working demo here.

Brian Rogers
  • 125,747
  • 31
  • 299
  • 300
0

The problem isn't your code so much as it is the JSON string that is coming back from the HttpClient. Because it has parts that don't have a key, it is hard to sort through, also each child of statistic is a child of that child (If my eyes don't deceive me).

However, using the code below, I am able to read the Statistics values singularly

JObject jo = JObject.Parse(json);
            var jList = from values in jo["Project"]
                        select values;
            foreach (var j in jList)
            {
                var l = j["Statistics"].Children();
                foreach (var m in l.Children())
                {
                    string a = m.ToString();
                }
            }

On the first loop, m has the Date, On the second loop it has the sale. because there is no key for these, I don't know any other way to reference them.

A bit less code but similar result

JObject jo = JObject.Parse(json);
        for (int i = 0; i < jo.Count; i++)
        {
            var jList = from values in jo["Project"][i]["Statistics"]
                        select values;                
            foreach (var stat in jList)
            {
                //here stat looks like {["20140722", 32.22]}
            }

        }
Seige
  • 304
  • 5
  • 27
  • Arrgh.... that's what I was assuming all along and was thinking I was going crazy! This JSON feed is horrible. I am looking at your code now, so I should be reporting back soon, thx! – user118190 Aug 05 '14 at 05:10