1
\$\begingroup\$

I've written code to deserialize a JSON into an ExpandoObject. The only problem with the ExpandoObject is that if you try to access a member which doesn't exist, it throws an exception.

So I've written a wrapper for the ExpandoObject called SafeExpandoObject which basically implements a DynamicObject that encapsulates an ExpandoObject and when a member is accessed it returns a SafeExpandoObject wrapped response so that it can be recursively accessed without ever throwing an exception.

dynamic jsonMeta = JsonConvert.DeserializeObject<ExpandoObject>(jsonScript, new ExpandoObjectConverter());
jsonMeta = new SafeExpandoObject(jsonMetadata); // Rewrap the ExpandoObject in a SafeExpandoObject that doesn't throw exceptions when accessing unknown members
string x = jsonMeta?.a?.b?.c?.d ?? "defVal" // Access the values dynamically

The SafeExpandoObject has a method TryMemberGet which is called when a member on the object is accessed dynamically and TryMemberSet which is called when a value is assigned dynamically.

/// <summary>
/// A safe wrapper for an ExpandoObject that returns a null when trying to access an unknown member/property instead of throwing an exception
/// </summary>
public class SafeExpandoObject : DynamicObject
{

    private ExpandoObject _expandoObject;

    /// <summary>
    /// Default constructor, empty object
    /// </summary>
    public SafeExpandoObject()
    {
        _expandoObject = new ExpandoObject();
    }

    /// <summary>
    /// Initialization for a safe expando object wrapper
    /// </summary>
    /// <param name="originalExpandoObject">The original 'unsafe' ExpandoObject</param>
    public SafeExpandoObject(ExpandoObject originalExpandoObject)
    {
        _expandoObject = originalExpandoObject;
    }

    /// <summary>
    /// Invoked when a member of the object is accessed
    /// </summary>
    /// <param name="result">Set to a SafeExpandoObject/value if the member exists or null if it doesn't</param>
    /// <returns>true</returns>
    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        IDictionary<string, object> expandoDictionary = _expandoObject;

        if (expandoDictionary.ContainsKey(binder.Name))
        {
            dynamic dictObject = expandoDictionary[binder.Name];
            // Wrap the results of the dereference into a SafeExpandoObject so when a member is accessed again it come right back here
            result = CheckAndConvertToSafeExpando(dictObject);
            return true;
        }
        else // When we're accessing an unknown member return null but all mark the operation as success otherwise it'll throw an exception
        {
            result = null;
            return true; // Success
        }
    }

    /// <summary>
    /// Creates or replaces the property of a member in the object
    /// </summary>
    /// <returns>True</returns>
    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        IDictionary<string, object> expandoDictionary = _expandoObject;

        expandoDictionary[binder.Name] = value;
        return true;
    }

    /// <summary>
    /// Calls a method function assigned to the object
    /// </summary>
    /// <param name="result">Result of the method invoked</param>
    /// <returns>true</returns>
    public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
    {
        IDictionary<string, object> expandoDictionary = _expandoObject;
        dynamic method = expandoDictionary[binder.Name];
        result = method(args[0].ToString(), args[1].ToString());
        return true;
    }

    /// <summary>
    /// Set the key and value on the object
    /// </summary>
    private void SetMember(string name, object value)
    {
        IDictionary<string, object> expandoDictionary = _expandoObject;
        expandoDictionary[name] = value;
    }

    /// <summary>
    /// Iteratively converts the object into a SafeExpandoObject
    /// </summary>
    /// <param name="someObject">The object to the converted to a SafeExpandoObject</param>
    /// <returns>SafeExpandoObject</returns>
    private dynamic CheckAndConvertToSafeExpando(dynamic someObject)
    {
        // Iterate through the object looking for ExpandoObject and convert them to SafeExpandoObject
        if (someObject is ExpandoObject) // Check if it's an ExpandoObject
        {
            dynamic safeExpandoObject = new SafeExpandoObject();
            IDictionary<string, object> expandoDictionary = someObject as ExpandoObject;
            foreach (dynamic expandoKvp in expandoDictionary)
            {
                if (expandoKvp.Value is ExpandoObject) // If the property value is an ExpandoObject, recursively convert it
                    safeExpandoObject.SetMember(expandoKvp.Key, CheckAndConvertToSafeExpando(expandoKvp.Value));
                else if (expandoKvp.Value is IList) // If it's a list of items, convert them if needed
                {
                    dynamic expandoListItem = ((IList<dynamic>)expandoKvp.Value).Select(item => item).ToList(); // Make a shallow copy of the list so we don't mess with the original list, TODO: do we need a deep copy here since we will be creating new SafeExpandoObject for each item in the list?
                    for (int i = 0; i < expandoListItem.Count; i++) // Iterate through the list and update each item
                        expandoListItem[i] = CheckAndConvertToSafeExpando(expandoListItem[i]);
                    safeExpandoObject.SetMember(expandoKvp.Key, expandoListItem); // Finally copy the converted list to the new SafeExpandoObject
                }
                else
                    safeExpandoObject.SetMember(expandoKvp.Key, expandoKvp.Value); // Otherwise, copy the property directly
            }

            return safeExpandoObject; // Return the new SafeExpandoObject
        }
        else if (someObject is IList) // If it's a list then convert them if needed
        {
            dynamic safeExpandoListItem = ((IList<dynamic>)someObject).Select(item => item).ToList(); // Make a shallow copy of the list so we don't mess with the original list, TODO: do we need a deep copy here since we will be creating new SafeExpandoObject for each item in the list?
            for (int i = 0; i < safeExpandoListItem.Count; i++) // Iterate through the list and update each item
                safeExpandoListItem[i] = CheckAndConvertToSafeExpando(safeExpandoListItem[i]);
            return safeExpandoListItem; // Return the updated object
        }
        else
            return someObject; // Return the unmodified object
    }
}

So I have a few questions here:

1

Is the CheckAndConvertToSafeExpando method good? Its job is to recursively convert the ExpandoObject into a SafeExpandoObject. I can only foresee three types of objects within an ExpandoObject,

  1. ExpandoObject
  2. IList<>
  3. Everything else is just a value which can't be dereferenced further, so keep as it

Is my assumption above correct?

2

In the TryGetMember method I'm essentially converting the return object into an SafeExpandoObject so if it's dereferenced again it will be sent back to the same method. Am I missing anything here in the overall implementation or are there any bugs or unforeseen situations?

\$\endgroup\$

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.