144

What's the best practice for retrieving JSON values that may not even exist in C# using Json.NET?

Right now I'm dealing with a JSON provider that returns JSON that sometimes contains certain key/value pairs, and sometimes does not. I've been using (perhaps incorrectly) this method to get my values (example for getting a double):

if(null != jToken["width"])
    width = double.Parse(jToken["width"].ToString());
else
    width = 100;

Now that works fine, but when there are a lot of them it's cumbersome. I ended up writing an extension method, and only after writing it did I wonder whether maybe I was being stupid... anyways, here is the extension method (I only include cases for double and string, but in reality I have quite a few more):

public static T GetValue<T>(this JToken jToken, string key,
                            T defaultValue = default(T))
{
    T returnValue = defaultValue;

    if (jToken[key] != null)
    {
        object data = null;
        string sData = jToken[key].ToString();

        Type type = typeof(T);

        if (type is double)
            data = double.Parse(sData);
        else if (type is string)
            data = sData;

        if (null == data && type.IsValueType)
            throw new ArgumentException("Cannot parse type \"" + 
                type.FullName + "\" from value \"" + sData + "\"");

        returnValue = (T)Convert.ChangeType(data, 
            type, CultureInfo.InvariantCulture);
    }

    return returnValue;
}

And here's an example of using the extension method:

width = jToken.GetValue<double>("width", 100);

BTW, Please forgive what may be a really dumb question, since it seems like something there should be a built in function for... I did try Google, and Json.NET documentation, however I'm either inept at finding the solution to my question or it's not clear in the documentation.

svick
  • 236,525
  • 50
  • 385
  • 514
Paul Hazen
  • 2,361
  • 2
  • 16
  • 21

6 Answers6

250

This is pretty much what the generic method Value() is for. You get exactly the behavior you want if you combine it with nullable value types and the ?? operator:

width = jToken.Value<double?>("width") ?? 100;
svick
  • 236,525
  • 50
  • 385
  • 514
  • This doesn't work if "width" doesn't exists in the json and JToken is null – Deepak Jan 11 '18 at 06:07
  • 5
    @Deepak It does work if "width" doesn't exist. Of course it doesn't work if `jToken` is `null`, but that's not what the question asked. And you can easily fix that by using the null conditional operator: `width = jToken?.Value("width") ?? 100;`. – svick Jan 11 '18 at 11:50
  • 2
    `JToken.Value` throws an exception if the JToken is a JValue – Kyle Delaney Dec 02 '19 at 21:14
  • 2
    To handle casing: `string ver = ((JObject)token).GetValue("version", StringComparison.OrdinalIgnoreCase)?.Value();` https://stackoverflow.com/a/49886682/2874896 – Jim Aho Sep 30 '20 at 13:34
  • 1
    Like @KyleDelaney notes this code is dangerous. Will throw `InvalidOperationException` in cases when `JToken` type ([`JTokenType`](https://www.newtonsoft.com/json/help/html/t_newtonsoft_json_linq_jtokentype.htm)) is one of `JValue` or `Undefined` or `Null` – Kirsan Jan 25 '23 at 09:45
24

I would write GetValue as below

public static T GetValue<T>(this JToken jToken, string key, T defaultValue = default(T))
{
    dynamic ret = jToken[key];
    if (ret == null) return defaultValue;
    if (ret is JObject) return JsonConvert.DeserializeObject<T>(ret.ToString());
    return (T)ret;
}

This way you can get the value of not only the basic types but also complex objects. Here is a sample

public class ClassA
{
    public int I;
    public double D;
    public ClassB ClassB;
}
public class ClassB
{
    public int I;
    public string S;
}

var jt = JToken.Parse("{ I:1, D:3.5, ClassB:{I:2, S:'test'} }");

int i1 = jt.GetValue<int>("I");
double d1 = jt.GetValue<double>("D");
ClassB b = jt.GetValue<ClassB>("ClassB");
L.B
  • 114,136
  • 19
  • 178
  • 224
  • That is pretty cool, but I like the separation of concerns that only getting simple data types gives me. Although the notion of that separation is a little bit blurred when it comes to JSON parsing. Since I implement an observer / observable model (with mvvm as well), I tend to keep all my parsing in one place, and keep it simple (part of that is also the unpredictability of the data returned to me). – Paul Hazen Mar 14 '12 at 00:38
  • @PaulHazen I can not say that I understand you. Your question was `retrieving JSON values that may not even exist` and all I proposed was to change your `GetValue` method. I think it works as how you want – L.B Mar 14 '12 at 08:56
  • Hopefully I can be a little more clear this time. Your method works great, and would accomplish exactly what I want. However, the greater context not explained in my question is that the particular code I'm working on is code that I want to be highly transferable. While it's arguable that your method gets in the way, it introduces the ability to deserialize objects from GetValue, which is a pattern I want to avoid for the sake of moving my code to a platform that has a better JSON parser (say, Win8 for example). So, for what I asked, yes, your code would be perfect. – Paul Hazen Mar 17 '12 at 23:27
10

Here is how you can check if the token exists:

if (jobject["Result"].SelectToken("Items") != null) { ... }

It checks if "Items" exists in "Result".

This is a NOT working example that causes exception:

if (jobject["Result"]["Items"] != null) { ... }
Artur Alexeev
  • 318
  • 3
  • 7
4

You can simply typecast, and it will do the conversion for you, e.g.

var with = (double?) jToken[key] ?? 100;

It will automatically return null if said key is not present in the object, so there's no need to test for it.

Dave Van den Eynde
  • 17,020
  • 7
  • 59
  • 90
4

This takes care of nulls

var body = JObject.Parse("anyjsonString");

body?.SelectToken("path-string-prop")?.ToString();

body?.SelectToken("path-double-prop")?.ToObject<double>();
Max
  • 589
  • 5
  • 6
1

TYPE variable = jsonbody["key"]?.Value<TYPE>() ?? DEFAULT_VALUE;

e.g.

bool attachMap = jsonbody["map"]?.Value<bool>() ?? false;

Downhillski
  • 2,555
  • 2
  • 27
  • 39