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?
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
HasValuefor nullable properties on dynamic objects?The short answer is; you can't, without unboxing. As an example, you could unbox the
Guidproperty in your code as follows:The reason behind this can be explained by examining some of the characteristics of
dynamicandNullable:dynamicis a compile-time concept and doesn't exist at runtime. So under the hood, it's just anobject.dynamicvariable, any child property and\or method is inherentlydynamicas well.Nullable<>doesn't get boxed as-is. Instead, you'd get either a null object reference if it'snullor you'd get the underlying type (e.g.System.Guid).What this means for your example is that
persongets boxed as well as theGuidproperty. Since theGuidproperty is nullable, it gets boxed as a eitherSystem.Guid(if it's notnull) ornull, 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: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
ExpandoObjectyou could simply make your function recursive. For example: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, callToExpandowith 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
dynamiccompletely 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).