Search code examples
c#c#-4.0nested

Access a nested structure by path (i.e., "Model.NestedModel.ListOfThings[1]")


Given the following classes and data:

public class InnerExample
{
    public string Inner1 { get; set; }
}


public class Example
{
    public string Property1 { get; set; }
    public string Property2 { get; set; }
    public List<InnerExample> Inner { get; set; }
}

var a = new Example
{
    Property1 = "Foo",
    Property2 = "Bar",
    Inner = new List<InnerExample>
    {
      new InnerExample
      {
        Inner1 = "This is the value to change"
      }
   }
};

Is there any way to access the innermost data by path?

Is there any way to say...

a["Inner[0].Inner1"] = "New value"

In this particular case, I know for a fact that I will never be accessing a key that does not exist, so I'm not overly concerned about error checking.

(Sorry if this has been asked before. I did a few searches but quickly ran out of keywords to try.)


Solution

  • Thanks to the basic advice you gave me, Jon, I came up with a solution that works for my case.

    • There is no error checking
    • You must be setting a property, not an array element.
    • I'm sure there are more efficient ways to do this... I'm far from an expert on reflection.

      /// <summary>
      /// Take an extended key and walk through an object to update it.
      /// </summary>
      /// <param name="o">The object to update</param>
      /// <param name="key">The key in the form of "NestedThing.List[2].key"</param>
      /// <param name="value">The value to update to</param>
      private static void UpdateModel(object o, string key, object value)
      {
          // TODO:
          // Make the code more efficient.
      
          var target = o;
          PropertyInfo pi = null;
      
          // Split the key into bits.
          var steps = key.Split('.').ToList();
      
          // Don't walk all the way to the end
          // Save that for the last step.
          var lastStep = steps[steps.Count-1];
          steps.RemoveAt(steps.Count-1);
      
          // Step through the bits.
          foreach (var bit in steps)
          {
              var step = bit;
      
              string index = null;
      
              // Is this an indexed property?
              if (step.EndsWith("]"))
              {
                  // Extract out the value of the index
                  var end = step.IndexOf("[", System.StringComparison.Ordinal);
                  index = step.Substring(end+1, step.Length - end - 2);
      
                  // and trim 'step' back down to exclude it.  (List[5] becomes List)
                  step = step.Substring(0, end);
              }
      
              // Get the new target.
              pi = target.GetType().GetProperty(step);
              target = pi.GetValue(target);
      
              // If the target had an index, find it now.
              if (index != null)
              {
                  var idx = Convert.ToInt16(index);
      
                  // The most generic way to handle it.
                  var list = (IEnumerable) target;
                  foreach (var e in list)
                  {
                      if (idx ==0)
                      {
                          target = e;
                          break;
                      }
                      idx--;
                  }
              }
          }
      
          // Now at the end we can apply the last step,
          // actually setting the new value.
          if (pi != null || steps.Count == 0)
          {
              pi = target.GetType().GetProperty(lastStep);
              pi.SetValue(target, value);
          }
      }