How to check for nested property in BsonDocument - Contains doesn't work

1.7k views Asked by At

When I wrote code to grab a nested property, it fails when the entire path doesn't exist - which is fair enough bsonDoc["Meeting"]["Session"]["Time"]

I can't see any way to protect against this, writing bsonDoc.Contains("Meeting.Session.Time") returns false even when it exists

Writing bsonDoc.Contains("Time") also returns fails, even when it does exist, so it can't even check the field property at all, if it's nested... ?

The documentation and even the code contains no clue how to do what I need. Not sure it's possible.

Is there a way to write a guard clause for nested bson document properties - ie does the BsonDocument class have a nested key checking mechanism?

2

There are 2 answers

1
Yong Shun On BEST ANSWER

I don't think there is any method from BsonDocument that supports getting element/value with the nested property.

But you can implement the logic/extension method to deal with:

Concept:

  1. Split key by '.'.

  2. First-level search.

    2.1 First chainKey will search from BsonDocument via TryGetValue.

    2.2 If the key has existed, @value (BsonValue) will contain value (This is needed while querying for nested levels).

    2.3 If the key doesn't exist, break the loop logic.

  3. Second-level or further level search

    3.1 Concept is the same with 2, just will search from BsonValue (as stated in 2.2).

public static class BsonDocumentExtensions
{
    public static bool TryGetChainedValue(this BsonDocument doc, string key, out BsonValue @value)
    {
        @value = default;
        // Get key, value from passed BsonDocument for first time, then refer to BsonValue to get key, value for nested property
        bool first = true;
        
        try
        {
            if (String.IsNullOrWhiteSpace(key))
                return false;
        
            string[] chainedKeys = key.Split(".").ToArray();
            
            bool hasKey = false;
            foreach (var chainKey in chainedKeys)
            {       
                if (first)
                {
                    hasKey = doc.TryGetValue(chainKey, out @value);
                    
                    first = false;
                }
                else
                {
                    hasKey = (@value.ToBsonDocument()).TryGetValue(chainKey, out @value);
                }

                // Throw exception if key not existed.
                if (!hasKey)
                    throw new Exception();
            }
        
            return true;
        }
        catch
        {           
            @value = default;
            return false;   
        }
    }
}

Sample .NET Fiddle


The concept above is same as multiple if logic as below:

if (doc.TryGetValue("Meeting", out BsonValue meetingValue))
{
    if ((meetingValue.ToBsonDocument()).TryGetValue("Session", out BsonValue sessionValue))
    {
        if ((sessionValue.ToBsonDocument()).TryGetValue("Time", out BsonValue timeValue))
        {
            Console.WriteLine(timeValue);
        }
    }
}
0
Rod Limao On

I using the same concept, but a little different aproach.

  1. This code return the value it self, not if is cheked;
  2. This code don't verify for the first interation;
public static class BsonDocumentExtensions
{
    public static BsonValue GetNestedValue(this BsonDocument doc, string key)
    {
        try
        {
            if (String.IsNullOrWhiteSpace(key))
                return false;
        
            string[] chainedKeys = key.Split(".").ToArray();
            BsonValue result = doc.GetValue(chainedKeys[0]);
            var regexIndex = new Regex(@"\d+");
            
            for (int i = 1; i < chainedKeys.Count(); i++)
            {
                if (regexIndex.IsMatch(chainedKeys[i]))
                {
                    int index = regexIndex.Match(chainedKeys[i]).Value.ToIns32();
                    result = result[index].ToBsonDocument();
                    continue;
                }

                result = (result.ToBsonDocument()).GetValue(chainedKeys[i]);
            }
        
            return result;
        }
        catch (Exception)
        {           
            throw;
        }
    }
}

Then you can call this extension like this

bsonDoc.GetNestedValue("Meeting.Session.Time")

If you know you have lists (arrays) in BsonDocument, than you should do like bellow

bsonDoc.GetNestedValue("Meeting.[0].Session.Time") bsonDoc.GetNestedValue("Meeting.Session.[3].Time")