MAK
MAK

Reputation: 1298

How to determine if a node exists in after dynamic parsing of JSON

I have the following piece of code which reads incoming message from an event hub and stores it in blob storage.

dynamic msg = JObject.Parse(myEventHubMessage);
WriteToBlob(msg, enqueuedTimeUtc, myEventHubMessage, binder, log);

Here are some examples of the JSON I receive:

{
  "deviceId": "ATT",
  "product": "testprod",
  "data": {
    "001": 1,
    "002": 3.1,
    "003": {
      "lat": 0,
      "lng": 0
    },
    "000": -80
  },
  "ts": "2020-01-27T19:29:34Z"
}
{
      "deviceId": "ATT",
      "product": "testprod",
      "data_in": {
        "ts": "2020-01-27T19:29:34Z",
        "001": 1,
        "002": 3.1,
        "003": {
          "lat": 0,
          "lng": 0
        },
        "000": -80
      }
    }

Now, instead of a 'data' node in the JSON, sometimes the device sends the node with the name 'data_in'. And the ts field can sometimes be inside or outside the data or data_in node, and maybe named timestamp. How can I efficiently determine if a node exists or not?

I was thinking of doing something like this:

if (msg.data_in.ts != null)
{
}

And I would do the same for all conditions. Is there a way to better achieve this? Also, the if condition fails if I check msg.data_in.ts if data_in node doesn't exist.

Upvotes: 1

Views: 299

Answers (1)

dbc
dbc

Reputation: 117344

Your problem is that you have upcast your JObject to dynamic. This makes things difficult for the following reasons:

  • You loose all compile-time checking for code correctness.

  • You loose convenient access to the methods and properties of JObject itself (as opposed to the dynamically provided JSON properties).

    Since JObject implements interfaces such as IDictionary<string, JToken> leaving it as a typed object will make the job of checking for, adding and removing select JSON properties easier.

To see why working with a typed JObject can be easier, first introduce the following extension methods for convenience:

public static class JsonExtensions
{
    public static JProperty Rename(this JProperty old, string newName)
    {
        if (old == null)
            throw new ArgumentNullException();
        var value = old.Value;
        old.Value = null;   // Prevent cloning of the value by nulling out the old property's value.
        var @new = new JProperty(newName, value);
        old.Replace(@new);  // By using Replace we preserve the order of properties in the JObject.
        return @new;
    }

    public static JProperty MoveTo(this JToken token, JObject newParent)
    {
        if (newParent == null || token == null)
            throw new ArgumentNullException();
        var toMove = (token as JProperty ?? token.Parent as JProperty);
        if (toMove == null)
            throw new ArgumentException("Incoming token does not belong to an object.");
        if (toMove.Parent == newParent)
            return toMove;
        toMove.Remove();
        newParent.Add(toMove);
        return toMove;
    }
}

And now you can normalize your messages as follows:

var msg = JObject.Parse(myEventHubMessage);

msg.Property("data_in")?.Rename("data"); // Normalize the name "data_in" to be "data".
msg["data"]?["ts"]?.MoveTo(msg);         // Normalize the position of the "ts" property, it should belong to the root object
msg["data"]?["timestamp"]?.MoveTo(msg);  // Normalize the position of the "timestamp" property, it should belong to the root object
msg.Property("timestamp")?.Rename("ts"); // And normalize the name of the "timestamp" property, it should be "ts".

Demo fiddle here.

Upvotes: 1

Related Questions