1

I have this object hierarchy/graph:

Person:

(Name,string)
(Age,int?)
(Guid,Guid?)
(Interests,List<Interest>)

Interest:

(Name,string)
(IsProfession,bool?)
(RequiredSkills,List<RequiredSkill>)

Skill:

(Title,string)
(HoursToAccomplish,int?)

So, basically an instance of this represented in JSON would be:

{
  "name": "John",
  "age": 38,
  "guid": null,
  "interests": [
    {
      "name": "party",
      "isProfession": false,
      "requiredSkills": []
    },
    {
      "name": "painting",
      "isProfession": true,
      "requiredSkill": [
        {
          "title": "optics",
          "hoursToAccomplish": 75
        }
      ]
    }
  ]
}

Now in C#, I want to cast an instance of this object graph to ExpandoObject and then work with that dynamically. I've written this conversion method:

public ExpandoObject ToExpando(object @object)
{
    var properties = @object.GetType().GetProperties();
    IDictionary<string, object> expando = new ExpandoObject();
    foreach (var property in properties)
    {
        expando.Add(property.Name, property.GetValue(@object));
    }
    return (ExpandoObject)expando;
}

It works great for nested objects. But this changes the nullability of properties. Thus this line of code encounters error:

// Strongly-typed:
if (person.Guid.HasValue) {
   // logic
}

// Expando object:
if (person.Guid.HasValue) { // 'System.Guid' does not contain a definition for 'HasValue'
   // logic
}

What should I do?

  • Are you just interested in how to use `HasValue` when working with dynamic objects or are you interested in how to convert an entire object into a dynamic one? These are two different questions with two different answers. – eitamal Mar 05 '20 at 05:40
  • I think he's interested in the second part. He wants to convert an object graph **entirely** so that when he still has `HasValue` on nullable types. – Saeed Neamati Mar 05 '20 at 11:11

1 Answers1

0

As I've mentioned in my comment, you're asking two separate questions, which are only loosely related, but I'd do my best to explain the distinction and to answer them both:

Question #1: How would you use HasValue for nullable properties on dynamic objects?

The short answer is; you can't, without unboxing. As an example, you could unbox the Guid property in your code as follows:

((Guid?)person.Guid).HasValue

The reason behind this can be explained by examining some of the characteristics of dynamic and Nullable:

  • dynamic is a compile-time concept and doesn't exist at runtime. So under the hood, it's just an object.
  • When using a dynamic variable, any child property and\or method is inherently dynamic as well.
  • A Nullable<> doesn't get boxed as-is. Instead, you'd get either a null object reference if it's null or you'd get the underlying type (e.g. System.Guid).

What this means for your example is that person gets boxed as well as the Guid property. Since the Guid property is nullable, it gets boxed as a either System.Guid (if it's not null) or null, eliminating the nullability of that property (which could lead to several different runtime exceptions).

Another, perhaps simpler solution, would be to just compare it with null. For example:

person.Guid != null

Question #2: How would you convert an entire object graph to ExpandoObject?

Contrary to the previous question, the solution to this question is perhaps easier to grasp (while it could require more code to implement).

To recursively convert an entire object graph to ExpandoObject you could simply make your function recursive. For example:

public ExpandoObject ToExpando(object @object)
{
    var properties = @object.GetType().GetProperties();
    IDictionary<string, object> expando = new ExpandoObject();
    foreach (var property in properties)
    {
        var value = GetValueOrExpandoObject(@object, property);
        expando.Add(property.Name, value);
    }
    return (ExpandoObject)expando;
}

public object GetValueOrExpandoObject(object @object, PropertyInfo property)
{
    var value = property.GetValue(@object);
    if (value == null) return null;

    var valueType = value.GetType();
    if (valueType.IsValueType || value is string) return value;

    if (value is IEnumerable enumerable) return ToExpandoCollection(enumerable);

    return ToExpando(value);
}

public IEnumerable<ExpandoObject> ToExpandoCollection(IEnumerable enumerable)
{
    var enumerator = enumerable.GetEnumerator();
    while (enumerator.MoveNext())
    {
        yield return ToExpando(enumerator.Current);
    }
}

In this example, I'm (naively) checking for each property whether or not the value should be converted to ExpandoObject (e.g. if it's a value type) and if it is, call ToExpando with the property's value.

Please note that this is far from the ideal solution, but it shows how quickly this can get out of control. The more types you need to handle differently the more complex this would get, so if you end up implementing something like this, you should consider the visitor pattern.


Lastly, I'd like to note that I would in general strongly recommend you avoid using dynamic completely if at all possible since it is very expensive both from a technical standpoint and from a cognitive one (i.e. understanding what the code does and what could go wrong).

eitamal
  • 702
  • 8
  • 11