How to use null conditional operator in a dynamic JSON with JsonConvert.DeserializeObject
Asked Answered
S

2

4

I'm using Newtonsoft to deserialize an known JSON object and retrieve some values from it if they're present.

The crux is there is that object structure may keep changing so I'm using dynamic to traverse the structure and retrieve the values. Since the object structure keeps changing, I'm using the null conditional operator to traverse the JSON.

The code looks like this

dynamic jsonMeta = JsonConvert.DeserializeObject<dynamic>(jsonScript);
string gVal = jsonMeta.a?.b?.c?.d?.e?.f?.g?.Value ?? ""

The whole idea of this is to traverse the object in a null safe manner so that if a member doesn't exist, it evaluates to null and it assigns it a default value without throwing an exception. But what I'm seeing is that I get a exception 'Newtonsoft.Json.Linq.JValue' does not contain a definition for 'e' if that member d is null.

My understanding is that while the Value of d is null, it's of type JValue so that's why the null conditional operator doesn't work, but then it tries to access member e inside d it throws the exception.

So my question is how can I make this work in C#? Is there an easy way to access the JSON members without knowing the JSON structure in a single line or relatively easy way?

Strother answered 8/4, 2022 at 17:41 Comment(3)
This looks to be a near duplicate question of Null-coalescing operator returning null for properties of dynamic objects - just substitute "null coalescing" with "null conditional" and that answer applies here. But it seems you don't want to know why the null conditional operator doesn't work, you want a workaround, correct?Stylograph
So my question is how can I make this work in C#? Is there an easy way to access the JSON members without knowing the JSON structure in a single line or relatively easy way? -- you could use a JSONPath query using SelectToken().Stylograph
@Stylograph That's correct, I know the reason I'm looking for a solution on how to achieve the intent of using an null coalescing and null conditional (they're different but similar) operators in a dynamic JSON unknown structure. SelectToken again doesn't seem to check each member against a null from your example above. The idea is to check each member against a null value since any of them could potentially be a null at runtime and we don't know to end up with an exception, but instead handle it gracefully for each member if the structure has a null anywhere in the chain.Strother
S
2

Unfortunately due to the design limitation of NewtonSoft JSON.NET it cannot use null coalescing or null conditional operators when using it as a dynamic cast as I've shown in my question above.

One solution I found is to use System.Web.Helpers.Json, this implementation allows you to do what I'm trying to do above without running into the exception that JSON.NET throws because the way it evaluates the members at runtime leading to a simple way to access members of dynamic JSON structures. Plus you don't need to reference Value for a member, it's implicit.

using System.Web.Helpers.Json

dynamic jsonMeta = Json.Decode(jsonString);
string gVal = jsonMeta.a?.b?.c?.d?.e?.f?.g ?? ""

You however need to install the assemblies separately depending on which version of Visual Studio you're using (which is also required for JSON.NET). With VS 2019, it's very easy to install it with a single click through the IDE error assistant. More details about it here: Where can I find System.Web.Helpers, System.Web.WebPages, and System.Web.Razor?

The limitation of Json.Decode is that it cannot handle duplicate keys (throws an exception) and also has a maximum limit on then number of characters it can parse (MaxJsonLength error) which can be inconvenient to fix.

Strother answered 9/4, 2022 at 15:46 Comment(1)
Keep in mind Json.Decode does not support handling duplicate keys and will throw an errorStrother
S
0

Another option is to use a dynamic ExpandoObject while deserializing the JSON with Newtonsoft.Json. This allows you to use the null conditional and null coalascing operators on the dynamic object.

dynamic jsonMeta = JsonConvert.DeserializeObject<ExpandoObject>(jsonScript, new ExpandoObjectConverter());
string gVal = jsonMeta.a?.b?.c?.d ?? ""

But this has a limitation that if it accesses an non-existent member the ExpandoObject will throw an exception (it is a sealed class and this behavior is hardcoded). So to work around this behavior I wrote a custom SafeExpandoObject class which wraps around the ExpandoObject and will allow you to access all/non-existent members without having an exception thrown, it will return null instead when you try to access a member that doesn't exist.

You can find the code here: https://codereview.stackexchange.com/questions/287243/wrapping-an-expandoobject-within-a-safeexpandoobject

Use it like this:

dynamic jsonMeta = JsonConvert.DeserializeObject<ExpandoObject>(jsonScript, new ExpandoObjectConverter());
jsonMeta = new SafeExpandoObject(jsonMeta); // Rewrap the ExpandoObject in a SafeExpandoObject that doesn't throw exceptions
string gVal = jsonMeta.a?.b?.c?.d ?? ""

Another point to note is that you cannot use direct indexing with ExpandoObject like jsonMeta["@name"].

To overcome this limitation you will need to either implement an IDictionary interface to the SafeExpandoObject to allow for direct indexing or cast the ExpandoObject it to a IDictionary<string, object> and then index it like

dynamic jsonMeta = JsonConvert.DeserializeObject<ExpandoObject>(jsonScript, new ExpandoObjectConverter());
string a = (jsonMeta as IDictionary<string, object>)["@name"];
Strother answered 28/9, 2023 at 18:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.