Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I derive a C# dynamic object from a web api JSON reponse?

Most of the time we don't, by default, have defined classes in our static language of choice, which presents white a quandary over how to deserialize the e.g. JSON returned from a call to a public and open wen api, dedicated to no single language, except the "language" of e.g. HTML and JSON.

In .NET, these days, nearly all api queries are done with HttpClient, something like the Google Books API ISBN query below:

public class GoogleBooksClient
{
    private const string IsbnUrl = "books/v1/volumes?q=isbn:{0}";
    private static HttpClient _client = new HttpClient();
    ...
    public async Task<object> GetDetailsByIsbn(string isbn)
    {
        var json = await _client.GetStringAsync(string.Format(IsbnUrl, isbn));
        dynamic objX = JsonConvert.DeserializeObject(json);
        return objX;
    }
}

The biggest problem here is that whether objX is declared var', object, or dynamic, it is always a reference to a big, ugly JObject instance when DeserializeObject is called without a known type. In this case, the JSON object returned is incredibly complex, and there be dragons waiting for those who endeavour to write a C# class that the said JSON can be parsed into.

This is an ideal, and intended, opportunity for the use of a C# dynamic object, where as the JSON is parsed, properties (and rarely functions for API responses) can be recursively added to the said dynamic object.

Instead, NewtonSoft.JSON tightly binds the data held by the JSON with the heavy red-tape ligature of a quite opaque Jobject data structure. I'm surprised and disappointed that having come so far, NewtonSoft cannot simply extract a pure dynamic object, without all the obfuscating bureaucracy of their JObject maze.

Is there no other way to simply parse the JSON into a C# dynamic object, just like it would be parsed into an explicitly dynamic JavaScript object in other scenarios?

ADDED: By "obfuscating bureaucracy of their JObject maze", I mean the following code:

var json = JsonConvert.SerializeObject(new Person {Id = 196912135012878, Age = 47, Name = "Brady"});
var jObj = JsonConvert.DeserializeObject(json);

produces a JObject instance that my debugger shows as:

Debugger view of JObject properties

This looks, to me, much more like something that should be used internally during parsing of the JSON, and not an end product of that parsing, which should be a pure representation of only the properties parsed from that JSON. That would ideally be a C# dynamic object with no properties for use by any parser.

like image 539
ProfK Avatar asked Feb 24 '17 03:02

ProfK


1 Answers

Rather than a JObject, you can deserialize to an ExpandoObject, which is supported by Json.NET's built-in converter ExpandoObjectConverter. If an array, you can deserialize to a List<ExpandoObject> then select to a List<dynamic>. If a primitive value, you could return JValue.Value.

For instance, if you know in advance your JSON represents an object, just do:

dynamic dyn = JsonConvert.DeserializeObject<ExpandoObject>(json);

If you don't know in advance what the root JSON container might be, you can load it as a JToken and use the following extension method:

public static class JsonExtensions
{
    public static dynamic ToDynamic(this JToken token)
    {
        if (token == null)
            return null;
        else if (token is JObject)
            return token.ToObject<ExpandoObject>();
        else if (token is JArray)
            return token.ToObject<List<ExpandoObject>>().Cast<dynamic>().ToList();
        else if (token is JValue)
            return ((JValue)token).Value;
        else
            // JConstructor, JRaw
            throw new JsonSerializationException(string.Format("Token type not implemented: {0}", token));
    }
}

Then do:

dynamic dyn = JsonConvert.DeserializeObject<JToken>(json).ToDynamic();

Sample fiddle.

like image 68
dbc Avatar answered Nov 09 '22 03:11

dbc