Search code examples
javascriptc#jsondynamicexpandoobject

C# - Reapply the manipulation of JS Object in .Net Core


I'm searching here and I saw several similar questions, but most were created some years ago.

So, I will open the question/discussion with an easy example of JS about how this language makes easy the modification, read, write props in his objects.

Check the following code on JS:

const dynamicObject = {
  a: [1, 2],
  b: "String val",
  c: 10,
  d: { sa: 1, sb: null, sc: [1, 2, 3] }
};

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

dynamicObject["d"]["sd"] = null
dynamicObject["d"].se = null

// Modify props
const prop = 'a'
dynamicObject[prop].push(3)
dynamicObject.b += " ABCD"

// Modify children props of another prop
dynamicObject.d.sb = ["New", "Array"]
dynamicObject.d["sa"] += 5

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

// Read props
const propValue = dynamicObject[prop]
console.log(propValue)

const propValueString = dynamicObject.b
console.log(propValueString)

Check the online results here

I'm tried to reapply this method using C#:

using System;
using System.Collections.Generic;
using Newtonsoft.Json;

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

        var DO = (IDictionary<string, object>)dynamicObject;

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

        dynamicObject["d"]["sd"] = null;
        dynamicObject["d"].se = null;

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

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

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

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

        string result = JsonConvert.SerializeObject(dynamicObject);

        Console.WriteLine(result);
    }
}

.Net Fiddle example here

But obviously it doesn't work as expected.

I know that we can use the ExpandoObject or dynamic or to access to props via string param transforming the object to an IDictionary<string, object>. I don't want to focus the discussion on the difference that C# is a strongly typed language.

But, C# still doesn't implement any structure/object/library to easily manage the manipulation of objects like JS?


Solution

  • IMPORTANT:

    I made another version with some improvements: Version 2


    I made my own Class to make more dynamic the interaction with Object/Dynamic/ExpandoObjects using C# Net Core

    CHECK THE CODE HERE

    Here is the full 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;
            }
        }
    }
    

    Here is the example of the 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");
        }
    }
    

    This is the result in JSON:

    {
       "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"
                }
             }
          }
       }
    }
    

    What do you think? Any additional feedback or comments?

    Just a disclaimer, the sublevels in JSON is used in a single array.

    • Write: dynamicObject["parent", "node"] = "New field";
    • Read: dynamicObject["parent", "node"];

    And I cannot be able to make the implementation without using a string. Ex: dynamicObject["myProp"] = "propValue", instead of dynamicObject.myProp = "propValue"