2
\$\begingroup\$

IMPORTANT:

I made another version with some improvements: Version 2


Since some months ago I disagree with the native/code methods to manipulate dynamic or ExpandoObject in C# .Net Core, it's quite messy trying to manipulate, read, write props in ExpandoObject in a dynamic way.

So, searching on Internet I can't found a solution that satisfies my needs. I'm searching for your opinion and feedback to rate the following code and the results.

And help me to understand if, I'm trying to invent the hot water? or if exists something similar in the core.

...if not, and I'm in the right approach, I want to listen to suggestions on how to improve my code, just because I will implement in a REST API service, and any additional tweak that makes this class faster will be better.

So, the full and functional example could find it here

The C# code:

namespace FW {
    public class Expando
    {
        public Expando(dynamic value)
        {
            expando = ToExpando(value);
        }

        public ExpandoObject root { get => expando; }

        private ExpandoObject expando { get; set; }

        private ExpandoObject ToExpando(dynamic dynamicObject)
        {
            if ((dynamicObject as object).GetType().Name == "ExpandoObject") return dynamicObject;

            if (!(dynamicObject as object).GetType().IsGenericType) throw new Exception("No generic type");

            ExpandoObject expando = new ExpandoObject();

            ((object)dynamicObject)
            .GetType()
                .GetProperties()
                .ToList()
                .ForEach(p => expando.fwAddProperty(p.Name, p.GetValue(dynamicObject) as object));

            return expando;
        }

        public dynamic this[string prop]
        {
            get => expando.fwReadProperty(prop);
            set => expando.fwAddProperty(prop, value as object);
        }

        public dynamic this[params string[] props]
        {
            get
            {
                ExpandoObject returnValue = expando;

                foreach (string prop in props)
                {
                    var temp = returnValue.fwReadProperty(prop);

                    try { returnValue = ToExpando(temp); }
                    catch { return temp as object; }
                }

                return returnValue;
            }
            set
            {
                List<ExpandoObject> list = new List<ExpandoObject>();
                list.Add(expando);

                foreach (var prop in props)
                {
                    var newProp = list.Last().fwReadProperty(prop);

                    if (newProp != null)
                    {
                        try { list.Add(ToExpando(newProp)); }
                        catch { }
                    }
                    else if (prop != props.Last())
                    {
                        ExpandoObject expandoTemp = new ExpandoObject();
                        list.Add(expandoTemp);
                    }
                }

                List<string> nodeProps = props.ToList();
                list.Last().fwAddProperty(nodeProps.Last(), value as object);

                nodeProps.RemoveAt(nodeProps.Count - 1);

                ExpandoObject ExpandoTemp = list.Last();
                list.RemoveAt(list.Count - 1);

                while (list.Count != 0)
                {
                    var node = list.Last();
                    list.RemoveAt(list.Count - 1);

                    node.fwAddProperty(nodeProps.Last(), ExpandoTemp as object);
                    nodeProps.RemoveAt(nodeProps.Count - 1);

                    ExpandoTemp = node;
                }

                expando = ExpandoTemp;
            }
        }
    }

    public static class extExpandoObject
    {
        public static void fwAddProperty(this ExpandoObject expando, string propertyName, object propertyValue)
        {
            // ExpandoObject supports IDictionary so we can extend it like this
            var expandoDict = expando as IDictionary<string, object>;

            if (expandoDict.ContainsKey(propertyName))
                expandoDict[propertyName] = propertyValue;
            else
                expandoDict.Add(propertyName, propertyValue);
        }

        public static object fwReadProperty(this ExpandoObject expando, string propertyName)
        {
            // ExpandoObject supports IDictionary so we can extend it like this
            var expandoDict = expando as IDictionary<string, object>;

            if (expandoDict.ContainsKey(propertyName))
                return expandoDict[propertyName];
            else
                return null;
        }
    }
}

Implementation

public class Program
{
    public static void Main()
    {
        FW.Expando dynamicObject = 
            new FW.Expando(new
                           {
                               a = new int[] { 1, 2 },
                               b = "String val",
                               c = 10,
                               d = new { sa = 1, sb = "abv", sc = new int[] { 1, 2, 3 } }
                           });

        // Add new props
        const string newProp = "e";
        dynamicObject[newProp] = "New val";
        dynamicObject["f"] = false;

        dynamicObject["d", "sd"] = "SDSDSD";
        var a = dynamicObject["d", "sd"];

        dynamicObject["d", "se"] = null;

        // Modify props
        const string prop = "a";
        dynamicObject[prop] = (dynamicObject[prop] as int[]).Append(3).ToArray();
        dynamicObject["b"] += " ABCD";

        // Modify children props of another prop
        dynamicObject["d", "sb"] = new string[] { "New", "Array" };

        dynamicObject["d", "sa"] += 5;

        dynamicObject["d", "sa"] = new { dz = "ABA", zz = "WCC", ZXXX = new { Y1 = "1", Y2 = "2" } };

        dynamicObject["parent", "node"] = "New field";

        dynamicObject["parent-node", "node-lvl1", "node-lvl1.1"] = "P > 1 > 1.1";
        dynamicObject["parent-node", "node-lvl1", "node-lvl1.2"] = "P > 1 > 1.2";
        dynamicObject["parent-node", "node-lvl2", "node-lvl2.1"] = "P > 2 > 2.1";
        dynamicObject["parent-node", "m-node", "sub1", "sub2", "sub3"] = "3 Sublevels";

        // Read props
        object propValue = dynamicObject[prop];
        object propValueString = dynamicObject["b"];

        string result = Newtonsoft.Json.JsonConvert.SerializeObject(dynamicObject.root);

        // CHECK MORE EASILY THE RESULT: https://jsonformatter.curiousconcept.com/
        Console.WriteLine("\r\n" + result + "\r\n");
    }
}

JSON Result:

{
   "a":[
      1,
      2,
      3
   ],
   "b":"String val ABCD",
   "c":10,
   "d":{
      "sa":{
         "dz":"ABA",
         "zz":"WCC",
         "ZXXX":{
            "Y1":"1",
            "Y2":"2"
         }
      },
      "sb":[
         "New",
         "Array"
      ],
      "sc":[
         1,
         2,
         3
      ],
      "sd":"SDSDSD",
      "se":null
   },
   "e":"New val",
   "f":false,
   "parent":{
      "node":"New field"
   },
   "parent-node":{
      "node-lvl1":{
         "node-lvl1.1":"P > 1 > 1.1",
         "node-lvl1.2":"P > 1 > 1.2"
      },
      "node-lvl2":{
         "node-lvl2.1":"P > 2 > 2.1"
      },
      "m-node":{
         "sub1":{
            "sub2":{
               "sub3":"3 Sublevels"
            }
         }
      }
   }
}

Thanks and regards to taking the time to read it.

\$\endgroup\$
3
\$\begingroup\$

If you want a cleaner version of dynamic/ExpandoObject, I would go step by step and fix what you don't like. Let's say you want to do something close to a Javascript-like syntax:

obj = {};
obj.a = 123;
obj.b = "message";
obj.c = [ 456, "something", {} ];
obj.d = {
    x: 50,
    y: [ "a", "b", "c" ],
    z: null
};

obj.e = "eeee";
obj.d.z = { z: "zz" };
obj.d.y.Add("d");

Converting this to the equivalent syntax using out-of-the-box dynamic/ExpandoObject:

dynamic obj = new ExpandoObject();
obj.a = 123;
obj.b = "message";
obj.c = new List<dynamic> { 456, "something", new ExpandoObject() };
obj.d = new ExpandoObject();
obj.d.x = 50;
obj.d.y = new List<dynamic> { "a", "b", "c" };
obj.d.z = null;

obj.e = "eeee";
obj.d.z = new ExpandoObject();
obj.d.z.z = "zz";
obj.d.y.Add("d");

The first major issue with the syntax is that there is no shortcut syntax for creating an ExpandoObject and giving it properties like a normal object:

// Can't do this...
obj.d = new ExpandoObject()
{ 
    x = 50,
    y = new List<dynamic> { "a", "b", "c" },
    z = null
}

One attempt would be to create and then convert an anonymous object (like you did in your code), however this requires reflection which is slow and can bring on other challenges. Instead, I think the cleanest solution would be to create a helper for creating an ExpandoObject and initializing it in some way, before returning it:

public static class Dynamic
{
    public static dynamic Object(Action<dynamic> init)
    {
        var obj = new ExpandoObject();
        init(obj);
        return obj;
    }
}

This makes inline objects much nicer:

dynamic obj = new ExpandoObject();
obj.a = 123;
obj.b = "message";
obj.c = new List<dynamic> { 456, "something", new ExpandoObject() };
obj.d = Dynamic.Object(o =>
{
    o.x = 50;
    o.y = new List<dynamic> { "a", "b", "c" };
    o.z = null;
});

obj.e = "eeee";
obj.d.z = Dynamic.Object(o => o.z = "zz");
obj.d.y.Add("d");

From here, the syntax is very close, and I would only add a few things to make things more consistent in naming/style:

public static class Dynamic
{
    public static dynamic Object(Action<dynamic> init)
    {
        var obj = new ExpandoObject();
        init(obj);
        return obj;
    }

    public static dynamic Object() => Object(_ => {});

    public static List<dynamic> List(params dynamic[] items) => items.ToList();
}

Usage:

dynamic obj = Dynamic.Object();
obj.a = 123;
obj.b = "message";
obj.c = Dynamic.List(456, "something", Dynamic.Object());
obj.d = Dynamic.Object(o =>
{
    o.x = 50;
    o.y = Dynamic.List("a", "b", "c");
    o.z = null;
});

obj.e = "eeee";
obj.d.z = Dynamic.Object(o => o.z = "zz");
obj.d.y.Add("d");

Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(obj,
    Newtonsoft.Json.Formatting.Indented));

Output:

{
  "a": 123,
  "b": "message",
  "c": [
    456,
    "something",
    {}
  ],
  "d": {
    "x": 50,
    "y": [
      "a",
      "b",
      "c",
      "d"
    ],
    "z": {
      "z": "zz"
    }
  },
  "e": "eeee"
}

There are other strategies you could use to implement Dynamic.Object and the other helpers, but they would all depend on your own personal style/preferences. Let me know if there is some key functionality that this is missing, and I'd be happy to add in a few more helpers.

| improve this answer | |
\$\endgroup\$
2
\$\begingroup\$

I like the simplicity of this code. It may needs some improvements, but these are just to make it more flexible to be adopted to other projects (if it's intended for that).

For most part, you've put the main functionalities on an extension class, which is something I wouldn't do myself. I think It would be better if Add, Remove, Get, and Set functionalities to be inside the main class. Then, if you want to make an extension to them, just call them back. This would make them more maintainable and easy to expand for future projects.

Also, the code missing the null validations, which you must consider all the time.

Another thing that I've noticed, there are many boxing and unboxing (dynamic to object and vise versa). try to minimize these castings.

Final part, I would suggest readjusting the class to be a thin layer on top of ExpandoObject that meant to simplify its functionalities, and use interface to make a contract that would help in future uses.

Here is what I have in my mind, something might be useful (I hope) :

public interface IExpando
{
    void AddOrUpdateProperty(string propertyName, object propertyValue);

    dynamic GetProperty(string propertyName);

    bool RemoveProperty(string propertyName);

    IDictionary<string, dynamic> GetProperties();
    // any other properties or methods that you think it's a must have
}

public class Expando : IExpando
{
    private readonly ExpandoObject _root;

    public Expando(dynamic value) { _root = InitiateInstance(value); }

    public dynamic this[string propertyName] 
    {
        get => GetProperty(propertyName);
        set => AddOrUpdateProperty(propertyName, value);
    }

    private ExpandoObject InitiateInstance(dynamic value)
    {
        if (value is null) { throw new ArgumentNullException(nameof(value)); }

        if (value.GetType() == typeof(ExpandoObject)) { return value; }

        if (!value.GetType().IsGenericType) { throw new Exception("No generic type"); }

        _root = new ExpandoObject();

        (value as object)?
        .GetType()
            .GetProperties()
            .ToList()
            .ForEach(p => AddOrUpdateProperty(p.Name, p.GetValue(value)));

        return _root;
    }   

    public void AddOrUpdateProperty(string propertyName, object propertyValue) { ...  }

    public bool RemoveProperty(string propertyName) { ... }

    public dynamic GetProperty(string propertyName) { ... }

    public IDictionary<string, dynamic> GetProperties() => _root as IDictionary<string, dynamic>;

}

public static class ExpandoExtension
{
    public static Expando ToExpando(this ExpandoObject expando)
    {
        return new Expando(expando); 
    }
}

these are just a sketch, you can implement even something better, and make it a more suitable for an open-source library that would be easy to use. For the extension, my thought is just you want one extension to return a new Expando, with that you're enforcing the usage of your class.

Also, you can define a private Dictionary<string, dynamic> and use it instead of casting the expandoObject on each method, and just store the results inside the _root.

I hope this would be useful.

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.