5

I'm trying to parse a JSON response that includes something I'm not quite familiar with, nor have I seen in the wild that often.

Inside one of the JSON objects, there is a dynamically named JSON object.

In this example, there is a JSON object inside "bugs" named "12345" which correlates to a bug number.

{
   "bugs" : {
      "12345" : {
         "comments" : [
            {
               "id" : 1,
               "text" : "Description 1"
            },
            {
               "id" : 2,
               "text" : "Description 2"
            }
         ]
      }
   }
}

What I'm curious about is: What would be the most effective way to parse a dynamically-named JSON object like this?

Given some JSON Utility tools like

They will take a JSON response like the one above and morph it into classes like the following respectfully:

jsonutils

public class Comment
{
    public int id { get; set; }
    public string text { get; set; }
}

public class 12345
{
    public IList<Comment> comments { get; set; }
}

public class Bugs
{
    public 12345 12345 { get; set; }
}

public class Root
{
    public Bugs bugs { get; set; }
}

json2charp

public class Comment
{
    public int id { get; set; }
    public string text { get; set; }
}

public class __invalid_type__12345
{
    public List<Comment> comments { get; set; }
}

public class Bugs
{
    public __invalid_type__12345 __invalid_name__12345 { get; set; }
}

public class RootObject
{
    public Bugs bugs { get; set; }
}

The problem about this is that it generates a class with a dynamic name. Thus subsequent queries with other identifiers to this API would result in a failure because the name does not match up nor would a generated [JsonProperty("")] as it would contain the dynamic class name as per the generated examples above.

Although the JSON is valid, this seems to be a limitation with JSON that is formatted this way. Unfortunately I do not have any control on this JSON API, so I'm curious what the best way to approach this problem would be?

Jon Douglas
  • 13,006
  • 4
  • 38
  • 51
  • `12345` is not a valid class name, and could be a major problem with this approach if you can't control the property names. – Dan Wilson Jan 09 '17 at 20:06
  • @DanWilson Right, that's why I'm wondering what can be done about dynamic json objects given I have no control over the backing store. This seems to be an edge case of bad JSON practices? – Jon Douglas Jan 09 '17 at 20:09
  • Yeah, it's helpful to keep property names static and give them dynamic values. @michael-gunter's answer is probably the solution here. – Dan Wilson Jan 09 '17 at 20:13
  • Firebase outputs arrays in this format. See this [Firebase blog](https://firebase.googleblog.com/2014/04/best-practices-arrays-in-firebase.html) for the story. They even say arrays are evil. Really. – Vanquished Wombat Jan 09 '17 at 22:28

2 Answers2

4

Try Json.NET, available as a Nuget package (Newtonsoft.Json) or from http://www.newtonsoft.com/json.

Json.NET can perform class-based serialization/deserialization such as you show. It also provides a generic JObject and JToken classes for cases where the format of the Json is not known or not fixed at dev time.

Here's an example loading a json object from a file.

// load file into a JObject
JObject document;
using (var fileStream = File.OpenRead(someFilePath))
using (var streamReader = new StreamReader(fileStream))
using (var jsonReader = new JsonTextReader(streamReader))
    document = JObject.Load(jsonReader);

// read the JObject
var bugs = (JObject) document["bugs"];
foreach (var bugEntry in bugs)
{
    var bugID = bugEntry.Key;
    var bugData = (JObject) bugEntry.Value;
    var comments = (JArray) bugData["comments"];
    foreach (JObject comment in comments)
        Debug.Print(comment["text"]);
}
Michael Gunter
  • 12,528
  • 1
  • 24
  • 58
  • Can this not be done with `JsonConvert.DeserializeObject` given a correct backing model? This seems to be a very manual approach rather than allowing the backing model to define the constraints. Thank you for your quick answer! – Jon Douglas Jan 09 '17 at 20:02
  • I'm confused, @JonDouglas. Having a backing model seems to be at odds with having dynamic property names. You either know the property names ahead of time or you don't. – Dan Wilson Jan 09 '17 at 20:08
  • I was thinking that perhaps a `Converter` could parse through a dynamic named object like this and add it to the backing model. I was also experimenting with an `ExpandoObject` for this purpose, but it seems that this is just a limitation that I would manually have to account for. – Jon Douglas Jan 09 '17 at 20:11
  • At second glance, it appears that the object in question isn't really an object with properties, but rather a dictionary of key-item pairs. JSON doesn't differentiate them, but as @JonDouglas is finding, the way that you consume them is different. I tend to think that most JSON libraries for .NET support dictionary properties (as per Eugene's answer), so if you want strict class definitions, try that. If it's anything more squishy, try using something generic like JObject. – Michael Gunter Jan 09 '17 at 20:12
  • Thank you for your answer Michael, I found Eugene's updated answer to be the closest to what I was after. – Jon Douglas Jan 09 '17 at 20:57
3

Newtonsoft.Json JsonConvert can parse it as a Dictionary<String, Comments> provided with appropriate model classes:

public class Comment
{
    public int id { get; set; }
    public string text { get; set; }
}

public class Comments
{
    public List<Comment> comments { get; set; }
}

public class RootObject
{
    public Dictionary<String, Comments> bugs { get; set; }
}

That can be checked with:

var json = "{\r\n   \"bugs\" : {\r\n      \"12345\" : {\r\n         \"comments\" : [\r\n            {\r\n               \"id\" : 1,\r\n               \"text\" : \"Description 1\"\r\n            },\r\n            {\r\n               \"id\" : 2,\r\n               \"text\" : \"Description 2\"\r\n            }\r\n         ]\r\n      }\r\n   }\r\n}";

Console.WriteLine(json);

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

Console.WriteLine(obj.bugs["12345"].comments.First().text);
Community
  • 1
  • 1
Eugene Podskal
  • 10,270
  • 5
  • 31
  • 53
  • I'm marking this as the answer as this was the most straight-forward way to do this without manually unpacking the json. Thank you for your time and answer! – Jon Douglas Jan 09 '17 at 20:56
  • 1
    Dictionary works with `JavaScriptSerializer` as well, and with `DataContractJsonSerializer` with [`UseSimpleDictionaryFormat = true`](https://stackoverflow.com/questions/4861138/c-sharp-json-serialization-of-dictionary-into-keyvalue-instead-of-keyk/31532750#31532750). – dbc Jan 10 '17 at 02:01