78

TL;DR Version

I have a object of type JToken (but can also be a string) and I need to convert it into a Type contained in the type variable:

Type type = typeof(DateTime); /* can be any other Type like string, ulong etc */
var obj = jsonObject["date_joined"]; /* contains 2012-08-13T06:01:23Z+05:00 */
var result = Some_Way_To_Convert(type, obj);

The above result should be a DateTime object with the value given in date_joined.

Full Story

I'm using both RestSharp and Json.NET in a Windows Phone project and I'm stuck while trying to deserialize JSON responses from a REST API.

What I'm actually trying to accomplish is to write a generic method that can easily map my JSON response into my CLR entities, much like you can do with RestSharp already. The only problem is that the default RestSharp implementation isn't working for me and it fails to successfully parse the JSON since the response doesn't always return all the properties (I don't return fields which are null from the REST server).

That's why I decided to use Newtonsoft's Json.NET since it has a much more powerful Json deserializing engine. Unfortunately, it doesn't support fuzzy property/field names like RestSharp (or I haven't found any), so it also doesn't map correctly to my CLR entities when I use something like say JsonConvert.DeserializeObject<User>(response.Content).

Here's what my Json looks like (an example actually):

{
    "id" : 77239923,
    "username" : "UzEE",
    "email" : "uzee@email.net",
    "name" : "Uzair Sajid",
    "twitter_screen_name" : "UzEE",
    "join_date" : "2012-08-13T05:30:23Z05+00",
    "timezone" : 5.5,
    "access_token" : {
        "token" : "nkjanIUI8983nkSj)*#)(kjb@K",
        "scope" : [ "read", "write", "bake pies" ],
        "expires" : 57723
    },
    "friends" : [{
        "id" : 2347484",
        "name" : "Bruce Wayne"
    },
    {
        "id" : 996236,
        "name" : "Clark Kent"
    }]
}

And here's an example of my CLR entities:

class AccessToken 
{
    public string Token { get; set; }
    public int Expires { get; set; }
    public string[] Scope { get; set; }
    public string Secret { get; set; } /* may not always be returned */
}

class User
{
    public ulong Id { get; set; }
    public string UserName { get; set; }
    public string Email { get; set; }
    public string Name { get; set; }
    public string TwitterScreenName { get; set; }
    public DateTime JoinDate { get; set; }
    public float Timezone { get; set; }
    public bool IsOnline { get; set; } /* another field that might be blank e.g. */

    public AccessToken AccessToken { get; set; }

    public List<User> Friends { get; set; }
}

What I want is a simple way to parse the above JSON into the given CLR objects. I've looked around the RestSharp source code and have seen the JsonDeserializer code and I've been able to write a generic extension method DeserializeResponse<T> on JObject that should return a an object of type T. The intended use is something like this:

var user = JObject.Parse(response.Content).DeserializeResponse<User>();

The above method should parse the given Json Response to a User entity object. Here's an actual code snippet of what I'm doing in the DeserializeResponse<User> extension method (its based on RestSharp code):

public static T DeserializeResponse<T>(this JObject obj) where T : new()
{
    T result = new T();
    var props = typeof(T).GetProperties().Where(p => p.CanWrite).ToList();
    var objectDictionary = obj as IDictionary<string, JToken>;

    foreach (var prop in props)
    {
        var name = prop.Name.GetNameVariants(CultureInfo.CurrentCulture).FirstOrDefault(n => objectDictionary.ContainsKey(n));
        var value = name != null ? obj[name] : null;

        if (value == null) continue;

        var type = prop.PropertyType;

        if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
        {
            type = type.GetGenericArguments()[0];
        }

        // This is a problem. I need a way to convert JToken value into an object of Type type
        prop.SetValue(result, ConvertValue(type, value), null); 
    }

    return result;
}

I'm guessing that the conversion should be a really straightforward thing to do since its a trivial task. But I've been searching for a quite a while now and still haven't found a way to do it via Json.NET (and lets be honest, the documentation is kinda though to understand and lacks some examples).

Any help would really be appreciated.

Uzair Sajid
  • 2,135
  • 2
  • 21
  • 28
  • Any help here? This is the second time I've asked any question on SO and yet never got an answer :/ – Uzair Sajid Aug 13 '12 at 09:27
  • http://stackoverflow.com/questions/9589218/get-value-from-jtoken-that-may-not-exist-best-practices – Guillaume Aug 13 '12 at 12:06
  • @Guillaume Thanks. I had already read that before posting. The problem is that I'm writing a parser that should be able to handle any value it encounters so a simple `jToken.value()` won't work since I don't know it would be a `double`. I don't know the type before hand. – Uzair Sajid Aug 13 '12 at 19:32

4 Answers4

144

There is a ToObject method now.

var obj = jsonObject["date_joined"];
var result = obj.ToObject<DateTime>();

It also works with any complex type, and obey to JsonPropertyAttribute rules

var result = obj.ToObject<MyClass>();

public class MyClass 
{ 
    [JsonProperty("date_field")]
    public DateTime MyDate {get;set;}
}
Softlion
  • 12,281
  • 11
  • 58
  • 88
  • 2
    +1 This is a much cleaner way of doing it than calling `.ToString()` and then deserializing it. I'm not 100% sure what the difference is under the bonnet, but this is much nicer syntactically. – theyetiman Apr 21 '15 at 10:16
  • 5
    If you do not know the type as design time, there is an overloaded version of ToObject() that takes Type as a parameter. I agree. This is the cleanest solution. It really should be the "answer". – Robert H. Mar 10 '16 at 22:07
  • This still seems inflexible. I need to build an arbitrary object to record incoming json data and store it as a queryable mongo object. So I need something like `jobjectOfTypeString.ToObject ` <- that exact code doesnt work – Vincent Buscarello Oct 10 '18 at 18:15
  • 2
    ^ gives me the super informative and helpful error "Message [string]:"Unable to cast object of type 'Newtonsoft.Json.Linq.JValue' to type 'Newtonsoft.Json.Linq.JObject'." " – Vincent Buscarello Oct 10 '18 at 18:17
25
System.Convert.ChangeType(jtoken.ToString(), targetType);

or

JsonConvert.DeserializeObject(jtoken.ToString(), targetType);

--EDIT--

Uzair, Here is a complete example just to show you they work

string json = @"{
        ""id"" : 77239923,
        ""username"" : ""UzEE"",
        ""email"" : ""uzee@email.net"",
        ""name"" : ""Uzair Sajid"",
        ""twitter_screen_name"" : ""UzEE"",
        ""join_date"" : ""2012-08-13T05:30:23Z05+00"",
        ""timezone"" : 5.5,
        ""access_token"" : {
            ""token"" : ""nkjanIUI8983nkSj)*#)(kjb@K"",
            ""scope"" : [ ""read"", ""write"", ""bake pies"" ],
            ""expires"" : 57723
        },
        ""friends"" : [{
            ""id"" : 2347484,
            ""name"" : ""Bruce Wayne""
        },
        {
            ""id"" : 996236,
            ""name"" : ""Clark Kent""
        }]
    }";

var obj = (JObject)JsonConvert.DeserializeObject(json);
Type type = typeof(int);
var i1 = System.Convert.ChangeType(obj["id"].ToString(), type);
var i2 = JsonConvert.DeserializeObject(obj["id"].ToString(), type);
L.B
  • 114,136
  • 19
  • 178
  • 224
  • 3
    Both of these don't work. `System.Convert.ChangeType()` needs a third `IFormatProvider` parameter. And `JsonConvert.DeserializeObject()` throws an exception: "Unexpected character encountered while parsing value: U. Path '', line 0, position 0." – Uzair Sajid Aug 13 '12 at 19:54
  • @UzairSajid I tested those codes before posting. `IFormatProvider` is optional. For the second part. If you post your code+data I can try to work on it. – L.B Aug 13 '12 at 20:30
  • 3
    It didn't work exactly as you said (actually the method signature for `System.Convert.ChangeType()` is different on Windows Phone) because it needs a mandatory third `IFormatProvider` parameter. But I was able to get it working by passing a `null` as the third parameter and handling a few edge cases explicitly (like parsing a value of `"1"` to `bool true` and a `string` to `URI`) for now. Will see if it still works in the long term. – Uzair Sajid Aug 14 '12 at 09:14
  • Please consider marking the "ToObject" answer as the correct one, it is a much better solution. – Robert McLaws Jun 01 '16 at 16:48
1
var i2 = JsonConvert.DeserializeObject(obj["id"].ToString(), type);

throws a parsing exception due to missing quotes around the first argument (I think). I got it to work by adding the quotes:

var i2 = JsonConvert.DeserializeObject("\"" + obj["id"].ToString() + "\"", type);
barjac
  • 36
  • 2
1

I was able to convert using below method for my WebAPI:

[HttpPost]
public HttpResponseMessage Post(dynamic item) // Passing parameter as dynamic
{
JArray itemArray = item["Region"]; // You need to add JSON.NET library
JObject obj = itemArray[0] as JObject;  // Converting from JArray to JObject
Region objRegion = obj.ToObject<Region>(); // Converting to Region object
}
Chirag
  • 1,683
  • 2
  • 17
  • 26